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.
Index ¶
Examples ¶
Constants ¶
const ContextKey = "csrf_token"
ContextKey is the default context store key for the CSRF token.
Variables ¶
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 ¶
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")
}
}
Output:
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()
}
Output:
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",
})
}
Output:
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")
},
})
}
Output:
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",
})
}
Output:
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",
})
}
Output:
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,
})
}
Output:
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,
})
}
Output:
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,
})
}
Output:
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"},
})
}
Output:
func TokenFromContext ¶
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)
}
}
Output:
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 ¶
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")
}
}
Output:
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.