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
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(locked — set aftercookieOptionsspread),sameSite: 'Strict'(locked) sameSitecannot be overridden viacookieOptionson either cookie- Token cookie
httpOnly: falseand UUID cookiehttpOnly: truecannot 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 |
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 {compose, cookie, csrf} from '@centralping/ergo';
const csrfMiddleware = csrf({secret: process.env.CSRF_SECRET});
// Issue tokens on GETconst issuePipeline = compose( cookie(), {fn: csrfMiddleware.issue, setPath: 'csrf'},);
// Verify tokens on POSTconst verifyPipeline = compose( cookie(), {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
- Secure Mutations — Integrated CSRF + auth + idempotency workflow with client-side fetch wiring
API Reference
Section titled “API Reference”See the auto-generated csrf API docs.