csrf
Provides CSRF protection using HMAC-signed tokens. Unlike other
middleware, csrf() returns an object with two methods — issue and
verify — rather than a single middleware function. In ergo-router, the
pipeline builder automatically dispatches issue for safe methods
(GET, HEAD, OPTIONS) and verify for unsafe methods.
Pipeline stage: Authorization
When CSRF Applies
Section titled “When CSRF Applies”CSRF attacks exploit a browser behavior: cookies are automatically attached to every request to the cookie’s origin, regardless of which site initiated the request. A malicious page can submit a form or fire a fetch to your API and the browser will include the user’s session cookie — without the user’s knowledge. CSRF tokens prevent this by requiring a secondary proof of intent that the attacking site cannot access.
This means CSRF protection is tied to your authentication mechanism, not your API’s functionality:
| Auth mechanism | Client type | CSRF needed? | Rationale |
|---|---|---|---|
| Session cookie | Browser | Yes | Browsers auto-attach cookies — CSRF prevents forged submissions |
| Bearer token | Mobile/service | No | Tokens are explicitly attached by the client — not auto-sent by the browser |
| Cookie + Bearer (mixed) | Both | Per-route | Enable CSRF on cookie-auth routes, disable on Bearer-only routes |
| None (public) | Any | No | No credentials to protect |
For APIs that serve both browser and token-based clients, configure CSRF
per-route — enable it on cookie-authenticated routes and disable it on
Bearer-only routes with csrf: false. See
Mixed-Auth CORS & CSRF for the
complete implementation pattern.
For configuring authentication strategies (Bearer, Basic, API key), see the authorization middleware guide.
Import
Section titled “Import”import { csrf } from "@centralping/ergo";Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
secret | string | — | Required. HMAC secret for token signing |
cookieTokenName | string | 'CSRF-TOKEN' | Cookie name for the CSRF token |
headerTokenName | string | 'X-CSRF-TOKEN' | Request header name for the token |
cookieUuidName | string | 'CSRF-UUID' | Cookie name for the CSRF UUID |
encoding | string | 'base64' | Token encoding |
cookieOptions | object | {} | Cookie directives (note: httpOnly and sameSite are locked) |
Cookie Security
Section titled “Cookie Security”- Token cookie:
httpOnly: false(locked — client JS must read it),sameSite: 'Strict'(locked) - UUID cookie:
httpOnly: true(default from cookie module),sameSite: 'Strict'(locked) sameSitecannot be overridden viacookieOptionson either cookie- Token cookie
httpOnly: falsecannot be overridden (set aftercookieOptionsspread)
Return Value
Section titled “Return Value”The factory returns { issue, verify }:
issue(req, res, acc)— Sets token and UUID cookies on the jar (acc.cookies). No return value.verify(req, res, acc)— Compares the request header token against the cookie usingcrypto.timingSafeEqual(). Returnsundefinedon success. On failure, returns{response: {statusCode: 403, detail: 'CSRF verification failed'}}.
Error Responses
Section titled “Error Responses”| Status | Condition |
|---|---|
| 403 Forbidden | Missing header token, missing UUID cookie, or token verification fails |
import { compose, cookie, csrf } from "@centralping/ergo";
const csrfMiddleware = csrf({ secret: process.env.CSRF_SECRET });
// Issue tokens on GETconst issuePipeline = compose( {fn: cookie(), setPath: "cookies"}, {fn: csrfMiddleware.issue, setPath: "csrf"},);
// Verify tokens on POSTconst verifyPipeline = compose( {fn: cookie(), setPath: "cookies"}, {fn: csrfMiddleware.verify, setPath: "csrf"},);import createRouter from "@centralping/ergo-router";
const router = createRouter({ defaults: { cookie: true, csrf: { secret: process.env.CSRF_SECRET }, },});
// Browser route — inherits cookie + CSRF from defaults.// GET/HEAD/OPTIONS → issue (sets token cookies)// POST/PUT/PATCH/DELETE → verify (checks X-CSRF-TOKEN header)router.get("/account/settings", { execute: (req, res, acc) => ({ response: { body: { settings: { theme: "dark" } } }, }),});
router.post("/account/settings", { execute: async (req, res, acc) => ({ response: { body: { updated: true } }, }),});
// Bearer-only route — disables CSRF (token clients are not// vulnerable). cookie: false is optional since cookie middleware// has no effect without CSRF, but makes intent explicit.router.post("/api/webhooks", { csrf: false, cookie: false, execute: async (req, res, acc) => ({ response: { body: { received: true } }, }),});cookie: true is required alongside csrf — cookie middleware
populates acc.cookies, which both issue and verify depend on.
See Config Resolution
for how defaults, true, false, and per-route overrides interact.
RFC References
Section titled “RFC References”Related Recipes
Section titled “Related Recipes”- Mixed-Auth CORS & CSRF — Configuring CORS and CSRF together for browser and token-based clients
API Reference
Section titled “API Reference”See the auto-generated csrf API docs.