csrf

package
v0.85.0-pre.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 3, 2026 License: BSD-3-Clause Imports: 12 Imported by: 0

README

kit/csrf

github.com/vormadev/vorma/kit/csrf

csrf is a stateless CSRF middleware using a double-submit cookie pattern with encrypted token payloads.

Defense layers:

  • cookie + header token match
  • optional Origin/Referer allowlist checks
  • optional session binding (token bound to current session ID)

Import

import "github.com/vormadev/vorma/kit/csrf"

Quick Start

protector := csrf.NewProtector(csrf.ProtectorConfig{
	CookieManager: cookieMgr, // required
	SessionIDFunc: func(r *http.Request) string {
		s := sessionFromRequest(r)
		if s == nil {
			return ""
		}
		return s.ID
	},
	AllowedOrigins: []string{"https://app.example.com"},
	TokenTTL:       24 * time.Hour,
})

mux.Handle("/", protector.Middleware(appHandler))

Required Login/Logout Step

You must cycle the CSRF token whenever auth state changes:

  • login: cycle using new session ID
  • logout: cycle using empty session ID
if err := protector.CycleTokenWithWriter(w, r, newSessionID); err != nil {
	return err
}

if err := protector.CycleTokenWithWriter(w, r, ""); err != nil {
	return err
}

If this is skipped, legitimate requests can fail due to token/session mismatch.

Request Flow

Safe methods (GET, HEAD, OPTIONS, TRACE)
  • middleware issues a CSRF cookie if missing/invalid/expired/session-mismatched
  • if existing token is valid for current session, cookie is left unchanged
Unsafe methods (POST, PUT, PATCH, DELETE, ...)

Validation requires:

  • allowed origin/referer (if restrictions configured)
  • CSRF cookie exists and is non-empty
  • token payload decrypts/parses and is unexpired
  • header token (HeaderName) is present
  • header value equals cookie value (constant-time compare)
  • token session ID equals SessionIDFunc(r) (constant-time compare)

Failure And Self-Heal Behavior

Unsafe-method failures return 403 Forbidden.

When possible, middleware also self-heals by issuing a fresh cookie in the same 403 response.

Failure case 403 Self-heal cookie
Origin/Referer not allowed yes no
Cookie missing yes yes
Cookie present but empty yes no
Token parse/decrypt fails yes yes
Token expired/invalid payload yes yes
Header token missing yes no
Header token mismatch yes no
Session mismatch yes yes

Origin/Referer Rules

AllowedOrigins is optional.

  • If empty, no origin validation is performed.
  • If set and Origin is present, Origin is validated.
  • If Origin is absent and Referer is present, Referer is validated.
  • If both headers are absent, this layer allows the request.

AllowedOrigins entries must include scheme and host (for example https://app.example.com).

Configuration Defaults And Panics

NewProtector defaults:

  • TokenTTL = 4h when zero
  • CookieName = "csrf_token"
  • HeaderName = "X-CSRF-Token"

NewProtector panics when:

  • CookieManager == nil
  • SessionIDFunc == nil
  • TokenTTL < 0
  • an AllowedOrigins entry is malformed or missing scheme/host

Dev-mode guard:

  • if cookie manager is in dev mode, middleware panics when request host is not localhost/loopback.
  • cookie is managed via cookies.SecureCookie[payload]
  • cookie settings are fixed to SameSite=Lax and HttpOnly=false
  • cookie value itself is the token clients must echo in the configured request header

Public API Reference

Types
  • type ProtectorConfig struct
  • type Protector struct
Exported Fields (ProtectorConfig)
  • CookieManager *cookies.Manager
  • SessionIDFunc func(r *http.Request) string
  • AllowedOrigins []string
  • TokenTTL time.Duration
  • CookieName string
  • HeaderName string
Constructor
  • func NewProtector(cfg ProtectorConfig) *Protector
Methods
  • func (p *Protector) Middleware(next http.Handler) http.Handler
  • func (p *Protector) CycleTokenWithProxy(rp *response.Proxy, sessionID string) error
  • func (p *Protector) CycleTokenWithWriter(w http.ResponseWriter, r *http.Request, sessionID string) error

Documentation

Overview

Package csrf provides a robust, stateless, and layered CSRF protection middleware for Go. It implements the Double Submit Cookie pattern using AEAD-encrypted, HostOnly tokens, enhanced with defense-in-depth measures including Origin/Referer validation and session binding. Unlike some CSRF prevention patterns, this middleware works regardless of whether any user session exists, meaning it also protects pre-authentication POST-ish endpoints such as login and registration endpoints. Consumers must ensure that they call either CycleTokenWithProxy or CycleTokenWithWriter (as applicable) whenever sessions are created or destroyed (e.g., on login and logout).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Protector

type Protector struct {
	// contains filtered or unexported fields
}

func NewProtector

func NewProtector(cfg ProtectorConfig) *Protector

func (*Protector) CycleTokenWithProxy

func (p *Protector) CycleTokenWithProxy(
	rp *response.Proxy,
	sessionID string,
) error

CycleTokenWithProxy generates a new CSRF token and sets it as a cookie. Must be called on login (with sessionID) and logout (with empty sessionID).

func (*Protector) CycleTokenWithWriter

func (p *Protector) CycleTokenWithWriter(
	w http.ResponseWriter,
	r *http.Request,
	sessionID string,
) error

CycleTokenWithWriter generates a new CSRF token and sets it as a cookie. Must be called on login (with sessionID) and logout (with empty sessionID).

func (*Protector) Middleware

func (p *Protector) Middleware(next http.Handler) http.Handler

Middleware applies CSRF protection. In dev mode it panics if a request host is not localhost.

type ProtectorConfig

type ProtectorConfig struct {
	// REQUIRED: A configured cookie manager.
	CookieManager *cookies.Manager
	// REQUIRED: Gets the session ID for the current request. Return empty string if no session exists.
	// This enables automatic session binding validation and smart token cycling.
	SessionIDFunc  func(r *http.Request) string
	AllowedOrigins []string
	// Defaults to 4 hours, but this is too short for most apps. A good value is to set this to match
	// the TTL of your authentication sessions. It's also a good idea to have your app make any GET
	// request on window focus to refresh the CSRF token, to minimize failure cases for legitimate users.
	TokenTTL time.Duration
	// Do not prefix the name with "__Host-". Prefixing is handled internally.
	// Final cookie name will be "__{Host|Dev}-{CookieName}".
	// Defaults to "csrf_token".
	CookieName string
	HeaderName string // Defaults to "X-CSRF-Token"
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL