# Guards, CSRF, Rate Limits, and Turnstile

> Apply runtime auth controls before route handlers run

Package: Kyzen
Canonical: https://kuratchi.dev/docs/kyzen/guards-rate-limit-turnstile
Markdown: https://kuratchi.dev/docs/kyzen/guards-rate-limit-turnstile.md

These subsystems live as nested options on `kyzenAuthMiddleware({...})`. They run inside the auth middleware's pre-route check phase before route handlers: rate-limit, CSRF/origin checks, Turnstile, then guards.

```ts
// src/middleware.ts
kyzenAuthMiddleware({
  guards:    { /* see Guards below */ },
  csrf:      { /* see CSRF below */ },
  rateLimit: { /* see Rate limiting below */ },
  turnstile: { /* see Turnstile below */ },
});
```

## Guards

Guards are configured with:

- `paths`
- `exclude`
- `redirectTo`

Example:

```ts
guards: {
  paths: ['/admin/*', '/dashboard/*'],
  exclude: ['/auth/login'],
  redirectTo: '/auth/login',
}
```

Runtime helpers:

- `checkGuard()`
- `requireAuthGuard`

Important behavior:

`checkGuard()` only checks for the presence of the session cookie. It does not fully validate the session record. Route code should still call `getCurrentUser()` or `getAuth().getSession()` before trusting the request as authenticated.

## CSRF and origin checks

CSRF protection is opt-in because existing apps may use different credential route names. Configure it on routes that mutate auth state, such as sign-in and sign-up form actions:

```ts
csrf: {
  paths: ['/auth/signin', '/auth/signup'],
  trustedOrigins: ['https://app.example.com', 'https://*.tenant.example.com'],
}
```

Behavior:

- Cross-site top-level POST navigations without cookies are rejected using Fetch Metadata headers.
- Unsafe requests with cookies validate the `Origin` header against the request origin plus `trustedOrigins`.
- Wildcard trusted origins match tenant subdomains.

Runtime helpers:

- `checkCsrf()`
- `configureCsrf(config)`

## Rate limiting

Configure rate limits per route:

```ts
rateLimit: {
  defaultWindowMs: 60000,
  defaultMaxRequests: 10,
  kvBinding: 'RATE_LIMIT',
  routes: [
    {
      id: 'auth-signin',
      path: '/auth/signin',
      methods: ['POST'],
      maxRequests: 5,
      windowMs: 60000,
      message: 'Too many sign-in attempts.',
    },
  ],
}
```

Runtime helpers:

- `checkRateLimit()`
- `getRateLimitInfo(routeId)`

The limiter uses `cf-connecting-ip` and can persist counts in KV when configured, with an in-memory fallback per isolate.

## Turnstile

Configure Turnstile with:

- `secretEnv`
- `siteKeyEnv`
- `skipInDev`
- `routes`

Example:

```ts
turnstile: {
  secretEnv: 'TURNSTILE_SECRET',
  siteKeyEnv: 'TURNSTILE_SITE_KEY',
  routes: [
    {
      id: 'auth-signin',
      path: '/auth/signin',
      methods: ['POST'],
      expectedAction: 'signin',
    },
  ],
}
```

Runtime helpers:

- `checkTurnstile()`
- `verifyTurnstile(token, options)`

The verifier reads tokens from common headers or form fields, then calls Cloudflare's Turnstile verification endpoint.
