CSRF

ingenium.csrf protects state-changing requests against cross-site request forgery. It supports two storage modes - cookie-based double-submit (default, no session needed) and synchronizer-pattern (when you're already running sessions).

Usage

import { ingenium } from 'ingenium'

const app = ingenium()
app.use(ingenium.csrf({
  secret: process.env.CSRF_SECRET!,    // required for cookie storage
  storage: 'cookie',                    // 'cookie' (default) | 'session'
  cookie: { sameSite: 'lax', secure: true },
  ignoreMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'],
  // skip: (ctx) => ctx.path.startsWith('/api/webhooks/'),  // opt-out
}))

app.get('/form', (ctx) => {
  // ctx.csrfToken() returns the current token to embed in HTML / send to a JS client.
  return `<form method="POST" action="/submit">
    <input type="hidden" name="_csrf" value="${ctx.csrfToken()}">
    <button>Submit</button>
  </form>`
})

app.post('/submit', async (ctx) => {
  // CSRF middleware already validated; if we got here the token is good.
  const body = await ctx.body.json()
  return { ok: true, body }
})

Storage modes

  • cookie (default, no session needed) - double-submit cookie pattern with HMAC-signed tokens. The token is written to a non-HttpOnly cookie on safe requests; the client must echo it back via X-CSRF-Token (or X-XSRF-Token for Angular, or ?_csrf= query param) on unsafe requests. Same-origin policy plus HMAC verification together prevent forgery.
  • session - synchronizer pattern. Token stored on ctx.session.csrfToken; submitted token compared against it. Requires sessionMiddleware to run first; throws a clear developer error if missing.

Verification

Verification uses crypto.timingSafeEqual. Secret rotation supported (secret: ['new', 'old']). Failures throw IngeniumCsrfError (HTTP 403, code CSRF_FAILED) which the default error boundary serializes; catch in app.onError for custom handling.

Sessioned apps should opt in to storage: 'session'. The default is 'cookie' because it's self-contained, but if you're already running sessionMiddleware the synchronizer pattern is simpler (one cookie instead of two, and rotating the session secret rotates CSRF protection automatically). We don't auto-detect because middleware order shouldn't change semantics.

Where to next?