Documentation Index
Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The csrf middleware protects against Cross-Site Request Forgery attacks by generating and validating tokens. It ensures that form submissions originate from your application, not from malicious sites.
Use it when you have:
- HTML forms that modify data
- Server-rendered applications
- Any POST/PUT/DELETE endpoints accessed via browser
Installation
import "github.com/go-mizu/mizu/middlewares/csrf"
Quick Start
app := mizu.New()
// Production: secure cookies
app.Use(csrf.Protect([]byte("32-byte-secret-key-here-12345")))
// Development: insecure cookies (HTTP)
app.Use(csrf.ProtectDev([]byte("32-byte-secret-key-here-12345")))
Configuration
Options
| Option | Type | Default | Description |
|---|
Secret | []byte | required | Secret key for token generation |
TokenLength | int | 32 | Length of random token |
TokenLookup | string | "header:X-CSRF-Token" | Where to find token in request |
CookieName | string | "_csrf" | Name of CSRF cookie |
CookiePath | string | "/" | Cookie path |
CookieMaxAge | int | 86400 | Cookie max age in seconds |
CookieSecure | bool | false | Secure cookie flag |
CookieHTTPOnly | bool | true | HTTPOnly cookie flag |
SameSite | http.SameSite | Lax | SameSite cookie attribute |
ErrorHandler | func(*mizu.Ctx, error) error | - | Custom error handler |
SkipPaths | []string | - | Paths to skip CSRF validation |
The TokenLookup option uses "source:name" format:
| Source | Example | Description |
|---|
header | "header:X-CSRF-Token" | Read from request header |
form | "form:_csrf" | Read from form field |
query | "query:csrf" | Read from query parameter |
Examples
secret := csrf.GenerateSecret() // Or use a fixed 32-byte secret
app.Use(csrf.New(csrf.Options{
Secret: secret,
CookieSecure: true, // Use in production with HTTPS
}))
app.Get("/form", func(c *mizu.Ctx) error {
token := csrf.Token(c)
return c.HTML(200, `
<form method="POST" action="/submit">
<input type="hidden" name="_csrf" value="`+token+`">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
`)
})
app.Post("/submit", func(c *mizu.Ctx) error {
// CSRF validated automatically
return c.Text(200, "Form submitted!")
})
Using Template Field Helper
app.Get("/form", func(c *mizu.Ctx) error {
// TemplateField returns a complete hidden input element
field := csrf.TemplateField(c)
return c.HTML(200, `
<form method="POST" action="/submit">
`+field+`
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
`)
})
JavaScript AJAX Requests
app.Use(csrf.New(csrf.Options{
Secret: secret,
TokenLookup: "header:X-CSRF-Token",
}))
<!-- Get token from cookie or meta tag -->
<meta name="csrf-token" content="{{ .CSRFToken }}">
<script>
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/data', {
method: 'POST',
headers: {
'X-CSRF-Token': token,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
</script>
Skip API Routes
app.Use(csrf.New(csrf.Options{
Secret: secret,
SkipPaths: []string{
"/api/webhook", // External webhooks
"/api/public", // Public API endpoints
},
}))
Custom Error Handler
app.Use(csrf.New(csrf.Options{
Secret: secret,
ErrorHandler: func(c *mizu.Ctx, err error) error {
if err == csrf.ErrTokenMissing {
return c.HTML(403, `
<h1>Security Error</h1>
<p>Missing security token. Please try again.</p>
`)
}
return c.HTML(403, `
<h1>Security Error</h1>
<p>Invalid security token. Please refresh and try again.</p>
`)
},
}))
Multiple Token Sources
// Accept token from either header or form
app.Use(csrf.New(csrf.Options{
Secret: secret,
TokenLookup: "form:_csrf", // Primary: form field
}))
// Alternative: Check header if form field missing
// (Requires custom implementation)
Development vs Production
func setupCSRF(app *mizu.App, isDev bool) {
secret := []byte(os.Getenv("CSRF_SECRET"))
if isDev {
app.Use(csrf.ProtectDev(secret))
} else {
app.Use(csrf.Protect(secret))
}
}
API Reference
Functions
// New creates CSRF middleware with options
func New(opts Options) mizu.Middleware
// Protect creates middleware with secure cookies (production)
func Protect(secret []byte) mizu.Middleware
// ProtectDev creates middleware with insecure cookies (development)
func ProtectDev(secret []byte) mizu.Middleware
// GenerateSecret generates a secure random secret
func GenerateSecret() []byte
Token Functions
// Token extracts CSRF token from context
func Token(c *mizu.Ctx) string
// TemplateField returns HTML hidden input field
func TemplateField(c *mizu.Ctx) string
// TokenExpiry returns token expiration time
func TokenExpiry(opts Options) time.Time
Error Types
var (
ErrTokenMissing // CSRF token not found in request
ErrTokenInvalid // CSRF token validation failed
)
How It Works
- Token Generation: On GET requests, a token is generated and stored in a cookie
- Token Validation: On POST/PUT/DELETE requests, the token must be included
- Double Submit: The cookie token must match the request token
- HMAC Signature: Tokens are signed to prevent tampering
GET /form
← Sets _csrf cookie with token
← Provides token for form
POST /submit
→ Sends _csrf cookie (automatic)
→ Sends token in form/header
← Server validates both match
Technical Details
Token Generation
The middleware generates cryptographically secure tokens using the following process:
- Random Bytes: Generates random bytes of specified length (default 32) using
crypto/rand
- HMAC Signature: Creates an HMAC-SHA256 signature of the random bytes using the secret key
- Encoding: Encodes both the token and signature using base64 URL encoding
- Format: Combines them as
token.signature for tamper detection
// Token format: base64(random_bytes).base64(hmac_sha256(random_bytes))
token := generateToken(32, secret)
// Example: "a1b2c3d4...xyz.9f8e7d6c..."
Token Validation
Validation follows a multi-step process to ensure security:
- Constant-Time Comparison: Uses
subtle.ConstantTimeCompare to prevent timing attacks
- Cookie-Request Match: Verifies the cookie token exactly matches the request token
- Signature Verification: Decodes and validates the HMAC signature
- HMAC Equality: Uses
hmac.Equal for secure signature comparison
Safe vs Unsafe Methods
The middleware distinguishes between HTTP methods:
- Safe Methods (GET, HEAD, OPTIONS, TRACE): Generate or reuse tokens, no validation required
- Unsafe Methods (POST, PUT, DELETE, PATCH): Require valid token in both cookie and request
Context Storage
Tokens are stored in the request context using a private contextKey type to prevent collisions with other middleware or application code. This allows retrieval via the Token() function throughout the request lifecycle.
Path Skipping
The SkipPaths option uses a map for O(1) lookup performance, allowing specific routes (like webhooks or public APIs) to bypass CSRF validation entirely.
Security Considerations
- Secret Key - Use a strong, random 32-byte secret
- Cookie Settings - Use
Secure, HttpOnly, and SameSite in production
- Token Rotation - Tokens are per-session, not per-request
- HTTPS Required - Always use HTTPS in production
Recommended Production Settings
app.Use(csrf.New(csrf.Options{
Secret: []byte(os.Getenv("CSRF_SECRET")),
CookieSecure: true,
CookieHTTPOnly: true,
SameSite: http.SameSiteStrictMode,
CookieMaxAge: 3600, // 1 hour
}))
Best Practices
- Always include CSRF protection for forms
- Use secure cookies in production
- Skip CSRF for API endpoints using Bearer auth
- Regenerate token after login/logout
- Set appropriate cookie expiration
Testing
The CSRF middleware includes comprehensive test coverage for all functionality:
| Test Case | Description | Expected Behavior |
|---|
| Basic Functionality | | |
| Sets cookie on GET | GET request to protected endpoint | CSRF cookie is set with generated token |
| Rejects POST without token | POST request without CSRF token | Returns 403 Forbidden |
| Accepts POST with valid token | POST with matching cookie and header token | Request succeeds with 200 OK |
| Rejects POST with invalid token | POST with mismatched tokens | Returns 403 Forbidden |
| Token Lookup Sources | | |
| Form token lookup | POST with token in form field | Token extracted from form and validated |
| Query token lookup | POST with token in query parameter | Token extracted from query and validated |
| Header token lookup | POST with token in header (default) | Token extracted from header and validated |
| Path Skipping | | |
| Skips listed path | POST to path in SkipPaths | Request bypasses CSRF validation |
| Protects non-listed path | POST to path not in SkipPaths | CSRF validation required |
| Error Handling | | |
| Custom error handler | POST without token with ErrorHandler set | Custom error handler called with error |
| Default error handler | POST without token, no ErrorHandler | Returns 403 with error text |
| Helper Functions | | |
| TemplateField generation | Calling TemplateField() with context | Returns HTML hidden input with token |
| Protect middleware | Creating middleware with Protect() | Secure cookies enabled by default |
| ProtectDev middleware | Creating middleware with ProtectDev() | Insecure cookies for development |
| GenerateSecret uniqueness | Generating multiple secrets | Each secret is unique and 32 bytes |
| Validation Logic | | |
| Valid token validation | Validating matching tokens with correct secret | Returns true |
| Mismatched tokens | Validating different tokens | Returns false |
| Invalid signature | Validating token with wrong secret | Returns false |
| Malformed token | Validating token without signature separator | Returns false |
| Token Generation | | |
| Token format | Generated token structure | Contains random bytes and HMAC signature separated by ”.” |
| Token uniqueness | Generating 100 tokens | All tokens are unique |
| Configuration Validation | | |
| Panics without secret | Creating middleware with empty secret | Panics with error message |
| Panics with invalid TokenLookup | Creating middleware with malformed TokenLookup | Panics with error message |