csrf

package
v1.3.2 Latest Latest
Warning

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

Go to latest
Published: Apr 6, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package csrf provides Cross-Site Request Forgery protection middleware for celeris using the double-submit cookie pattern, with optional server-side token storage for enhanced security.

On safe HTTP methods (GET, HEAD, OPTIONS, TRACE by default) the middleware generates or reuses a CSRF token, sets it as a cookie, and stores it in the request context. On unsafe methods (POST, PUT, DELETE, PATCH) it performs defense-in-depth checks (Sec-Fetch-Site, Origin, Referer) then compares the cookie token against the request token using constant-time comparison.

Default usage (token in X-CSRF-Token header):

server.Use(csrf.New())

Custom token lookup from a form field:

server.Use(csrf.New(csrf.Config{
    TokenLookup: "form:_csrf",
}))

Retrieving the Token

Use TokenFromContext to read the token in downstream handlers:

token := csrf.TokenFromContext(c)

Server-Side Token Storage

For enhanced security, configure Config.Storage to validate tokens against a server-side store (signed double-submit):

store := csrf.NewMemoryStorage()
server.Use(csrf.New(csrf.Config{
    Storage:    store,
    Expiration: 2 * time.Hour,
}))

Session Cookies

Set Config.CookieMaxAge to 0 for a browser-session-scoped cookie:

server.Use(csrf.New(csrf.Config{
    CookieMaxAge: 0,
}))

Trusted Origins

Config.TrustedOrigins allows cross-origin requests from specific origins. Wildcard subdomain patterns are supported:

server.Use(csrf.New(csrf.Config{
    TrustedOrigins: []string{
        "https://app.example.com",
        "https://*.example.com",
    },
}))

Sentinel Errors

The package exports sentinel errors (ErrForbidden, ErrMissingToken, ErrTokenNotFound, ErrOriginMismatch, ErrRefererMissing, ErrRefererMismatch, ErrSecFetchSite) for use with errors.Is.

Security

CookieSecure defaults to false for development convenience. Production deployments MUST set CookieSecure: true to prevent cookie transmission over unencrypted connections.

CookieHTTPOnly is always enforced as true regardless of the user-supplied Config value. This prevents client-side JavaScript from reading the CSRF cookie, which is a defense-in-depth measure against XSS token theft.

Method Override Interaction

When using the methodoverride middleware (registered via Server.Pre()), the HTTP method is rewritten before CSRF validation. A POST request with X-HTTP-Method-Override: PUT becomes a PUT by the time CSRF runs.

IMPORTANT: Do not add PUT, DELETE, or PATCH to SafeMethods if you use method override. Doing so would allow form submissions to bypass CSRF token validation by tunneling through POST → PUT/DELETE/PATCH.

The default SafeMethods (GET, HEAD, OPTIONS, TRACE) are safe because methodoverride only overrides POST requests, and the typical override targets (PUT, DELETE, PATCH) are not in the CSRF safe set.

Index

Examples

Constants

View Source
const ContextKey = "csrf_token"

ContextKey is the default context store key for the CSRF token.

Variables

View Source
var (
	// ErrForbidden is returned when the submitted CSRF token does not match
	// the cookie token.
	ErrForbidden = celeris.NewHTTPError(403, "Forbidden")

	// ErrMissingToken is returned when the CSRF token is absent from the
	// cookie or the request source.
	ErrMissingToken = &celeris.HTTPError{Code: 403, Message: "Forbidden", Err: errors.New("csrf: missing token")}

	// ErrTokenNotFound is returned by DeleteToken when no token exists.
	ErrTokenNotFound = &celeris.HTTPError{Code: 404, Message: "Not Found", Err: errors.New("csrf: token not found")}

	// ErrOriginMismatch is returned when the Origin header does not match
	// the request host or any trusted origin.
	ErrOriginMismatch = &celeris.HTTPError{Code: 403, Message: "Forbidden", Err: errors.New("csrf: origin does not match")}

	// ErrRefererMissing is returned when the Referer header is absent on an
	// HTTPS request with no Origin header.
	ErrRefererMissing = &celeris.HTTPError{Code: 403, Message: "Forbidden", Err: errors.New("csrf: referer header missing")}

	// ErrRefererMismatch is returned when the Referer header does not match
	// the request host or any trusted origin.
	ErrRefererMismatch = &celeris.HTTPError{Code: 403, Message: "Forbidden", Err: errors.New("csrf: referer does not match")}

	// ErrSecFetchSite is returned when the Sec-Fetch-Site header value is
	// "cross-site", indicating a cross-site request.
	ErrSecFetchSite = &celeris.HTTPError{Code: 403, Message: "Forbidden", Err: errors.New("csrf: sec-fetch-site is cross-site")}
)

Sentinel errors returned by the CSRF middleware. These are package-level variables for use with errors.Is. Do not reassign them.

Functions

func DeleteToken

func DeleteToken(c *celeris.Context) error

DeleteToken removes the CSRF token from server-side storage and expires the cookie. This is a convenience wrapper around HandlerFromContext. Returns ErrTokenNotFound if no token or handler exists.

Example
package main

import (
	"github.com/goceleris/celeris"

	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Delete token during logout.
	_ = func(c *celeris.Context) error {
		_ = csrf.DeleteToken(c)
		return c.String(200, "logged out")
	}
}

func New

func New(config ...Config) celeris.HandlerFunc

New creates a CSRF middleware with the given config.

Example
package main

import (
	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Default: token extracted from X-CSRF-Token header.
	_ = csrf.New()
}
Example (CustomContextKey)
package main

import (
	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Store token under a custom context key.
	_ = csrf.New(csrf.Config{
		ContextKey: "my_csrf_token",
	})
}
Example (CustomErrorHandler)
package main

import (
	"github.com/goceleris/celeris"

	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Custom error handling for CSRF failures.
	_ = csrf.New(csrf.Config{
		ErrorHandler: func(_ *celeris.Context, _ error) error {
			return celeris.NewHTTPError(403, "CSRF validation failed")
		},
	})
}
Example (FormLookup)
package main

import (
	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Extract token from a form field instead of a header.
	_ = csrf.New(csrf.Config{
		TokenLookup: "form:_csrf",
	})
}
Example (MultipleExtractors)
package main

import (
	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Try header first, then fall back to form field.
	_ = csrf.New(csrf.Config{
		TokenLookup: "header:X-CSRF-Token,form:_csrf",
	})
}
Example (ServerSideStorage)
package main

import (
	"time"

	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Enable server-side token storage for enhanced security.
	store := csrf.NewMemoryStorage()
	_ = csrf.New(csrf.Config{
		Storage:    store,
		Expiration: 2 * time.Hour,
	})
}
Example (SessionCookie)
package main

import (
	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Browser-session-scoped cookie (MaxAge 0).
	_ = csrf.New(csrf.Config{
		CookieMaxAge: 0,
	})
}
Example (SingleUseToken)
package main

import (
	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Single-use tokens: each token is consumed after validation.
	_ = csrf.New(csrf.Config{
		Storage:        csrf.NewMemoryStorage(),
		SingleUseToken: true,
	})
}
Example (WildcardTrustedOrigins)
package main

import (
	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Allow all subdomains of example.com.
	_ = csrf.New(csrf.Config{
		TrustedOrigins: []string{"https://*.example.com"},
	})
}

func TokenFromContext

func TokenFromContext(c *celeris.Context) string

TokenFromContext returns the CSRF token from the context store. Returns an empty string if no token was stored (e.g., skipped request). When the middleware is configured with a custom ContextKey, the handler's key is tried first, falling back to the default ContextKey constant.

Example
package main

import (
	"github.com/goceleris/celeris"

	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Retrieve the CSRF token inside a handler to pass to templates.
	_ = func(c *celeris.Context) error {
		token := csrf.TokenFromContext(c)
		return c.String(200, "token: %s", token)
	}
}

Types

type Config

type Config struct {
	// Skip defines a function to skip this middleware for certain requests.
	Skip func(c *celeris.Context) bool

	// SkipPaths lists paths to skip (exact match).
	SkipPaths []string

	// TokenLength is the number of random bytes used to generate the token.
	// The resulting hex-encoded token string is twice this length.
	// Default: 32.
	TokenLength int

	// TokenLookup defines the source(s) for extracting the CSRF token on
	// unsafe methods. Format is "source:name" where source is "header",
	// "form", or "query". Multiple sources can be comma-separated; the
	// first non-empty match is used. Default: "header:X-CSRF-Token".
	TokenLookup string

	// CookieName is the name of the CSRF cookie. Default: "_csrf".
	CookieName string

	// CookiePath is the path attribute of the CSRF cookie. Default: "/".
	CookiePath string

	// CookieDomain is the domain attribute of the CSRF cookie.
	CookieDomain string

	// CookieMaxAge is the max-age of the CSRF cookie in seconds.
	// Default: 86400 (24 hours). Set to 0 for a session cookie
	// (deleted when the browser closes).
	CookieMaxAge int

	// CookieSecure flags the cookie for HTTPS-only transmission.
	CookieSecure bool

	// CookieHTTPOnly controls the HttpOnly flag on the CSRF cookie.
	// This is always enforced as true for security — CSRF cookies must not
	// be readable by JavaScript. The field exists for documentation purposes
	// but is overridden by applyDefaults.
	CookieHTTPOnly bool

	// CookieSameSite controls the SameSite attribute of the CSRF cookie.
	// Default: celeris.SameSiteLaxMode.
	CookieSameSite celeris.SameSite

	// ErrorHandler handles CSRF validation failures. When nil, the
	// middleware returns the error directly (ErrMissingToken or ErrForbidden).
	ErrorHandler func(c *celeris.Context, err error) error

	// SafeMethods lists HTTP methods that do not require token validation.
	// Default: ["GET", "HEAD", "OPTIONS", "TRACE"].
	SafeMethods []string

	// TrustedOrigins lists additional origins that are allowed for
	// cross-origin requests. Each entry should be a full origin
	// (e.g. "https://app.example.com") or a wildcard subdomain pattern
	// (e.g. "https://*.example.com"). When the Origin header is present
	// on unsafe methods, it must match the request Host or one of these
	// entries. An empty list means only same-origin requests are allowed.
	TrustedOrigins []string

	// Storage enables server-side token storage (signed double-submit).
	// When set, tokens are stored in the backend and validated against
	// the store on unsafe methods. When nil, pure double-submit cookie
	// mode is used.
	Storage Storage

	// Expiration is the token lifetime in server-side storage.
	// Only used when Storage is set. Default: 1 hour.
	Expiration time.Duration

	// SingleUseToken, when true and Storage is set, deletes the token
	// from storage after successful validation. A new token is generated
	// on the next safe-method request.
	SingleUseToken bool

	// KeyGenerator provides a custom token generation function.
	// When nil, the default buffered hex generator is used.
	KeyGenerator func() string

	// ContextKey is the key used to store the CSRF token in the
	// request context. Default: "csrf_token".
	ContextKey string
}

Config defines the CSRF middleware configuration.

type Handler

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

Handler provides methods for managing CSRF tokens for a specific middleware configuration. Retrieved via HandlerFromContext.

The fields are copied from Config at construction time rather than holding a Config reference. This is intentional for immutability: the caller cannot mutate Handler state after New() returns, and the middleware closure and Handler always see consistent values without synchronization.

func HandlerFromContext

func HandlerFromContext(c *celeris.Context) *Handler

HandlerFromContext returns the Handler associated with the CSRF middleware from the request context. Returns nil if the middleware has not run.

Example
package main

import (
	"github.com/goceleris/celeris"

	"github.com/goceleris/celeris/middleware/csrf"
)

func main() {
	// Retrieve handler for more control.
	_ = func(c *celeris.Context) error {
		h := csrf.HandlerFromContext(c)
		if h != nil {
			_ = h.DeleteToken(c)
		}
		return c.String(200, "done")
	}
}

func (*Handler) DeleteToken

func (h *Handler) DeleteToken(c *celeris.Context) error

DeleteToken removes the CSRF token from server-side storage and expires the cookie. Useful for logout flows. Returns ErrTokenNotFound if no token exists in the cookie.

type MemoryStorageConfig

type MemoryStorageConfig struct {
	// Shards is the number of lock shards. Default: runtime.NumCPU().
	Shards int

	// CleanupInterval is how often expired entries are evicted.
	// Default: 1 minute.
	CleanupInterval time.Duration

	// CleanupContext, if set, controls cleanup goroutine lifetime.
	// When the context is cancelled, the cleanup goroutine stops.
	// If nil, cleanup runs until the process exits.
	CleanupContext context.Context
}

MemoryStorageConfig configures the in-memory CSRF token store.

type Storage

type Storage interface {
	// Get retrieves a token by key. Returns the token and true if found
	// and not expired, or empty string and false otherwise.
	Get(key string) (string, bool)

	// Set stores a token with the given key and expiry duration.
	Set(key string, token string, expiry time.Duration)

	// Delete removes a token by key.
	Delete(key string)

	// GetAndDelete atomically retrieves and removes a token by key.
	// Returns the token, true, and nil if found and not expired.
	// Returns empty string, false, and nil if not found or expired.
	// Used for single-use token validation to prevent TOCTOU races.
	GetAndDelete(key string) (string, bool, error)
}

Storage defines the interface for server-side CSRF token storage. When configured, the middleware validates tokens against the store instead of relying solely on cookie comparison (signed double-submit).

func NewMemoryStorage

func NewMemoryStorage(config ...MemoryStorageConfig) Storage

NewMemoryStorage creates an in-memory CSRF token store backed by sharded maps. A background goroutine periodically evicts expired tokens.

Jump to

Keyboard shortcuts

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