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