> ## 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.

# CSRF

> Cross-Site Request Forgery protection middleware for secure form submissions.

## 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

```go theme={null}
import "github.com/go-mizu/mizu/middlewares/csrf"
```

## Quick Start

```go theme={null}
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 |

### TokenLookup Format

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

### Basic Form Protection

```go theme={null}
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

```go theme={null}
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

```go theme={null}
app.Use(csrf.New(csrf.Options{
    Secret:      secret,
    TokenLookup: "header:X-CSRF-Token",
}))
```

```html theme={null}
<!-- 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

```go theme={null}
app.Use(csrf.New(csrf.Options{
    Secret: secret,
    SkipPaths: []string{
        "/api/webhook",    // External webhooks
        "/api/public",     // Public API endpoints
    },
}))
```

### Custom Error Handler

```go theme={null}
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

```go theme={null}
// 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

```go theme={null}
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

```go theme={null}
// 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

```go theme={null}
// 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

```go theme={null}
var (
    ErrTokenMissing // CSRF token not found in request
    ErrTokenInvalid // CSRF token validation failed
)
```

## How It Works

1. **Token Generation**: On GET requests, a token is generated and stored in a cookie
2. **Token Validation**: On POST/PUT/DELETE requests, the token must be included
3. **Double Submit**: The cookie token must match the request token
4. **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:

1. **Random Bytes**: Generates random bytes of specified length (default 32) using `crypto/rand`
2. **HMAC Signature**: Creates an HMAC-SHA256 signature of the random bytes using the secret key
3. **Encoding**: Encodes both the token and signature using base64 URL encoding
4. **Format**: Combines them as `token.signature` for tamper detection

```go theme={null}
// 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:

1. **Constant-Time Comparison**: Uses `subtle.ConstantTimeCompare` to prevent timing attacks
2. **Cookie-Request Match**: Verifies the cookie token exactly matches the request token
3. **Signature Verification**: Decodes and validates the HMAC signature
4. **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

1. **Secret Key** - Use a strong, random 32-byte secret
2. **Cookie Settings** - Use `Secure`, `HttpOnly`, and `SameSite` in production
3. **Token Rotation** - Tokens are per-session, not per-request
4. **HTTPS Required** - Always use HTTPS in production

### Recommended Production Settings

```go theme={null}
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 |

## Related Middlewares

* [secure](/middlewares/secure) - HTTPS enforcement
* [helmet](/middlewares/helmet) - Security headers


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.