Aller au contenu
Français

CSRF protection

Ce contenu n’est pas encore disponible dans votre langue.

csrfProtection is a stateless CSRF defence: the token is payload.hmac(secret, payload), carried in a __Host--prefixed cookie. Safe methods mint/refresh the token; unsafe methods (POST/PUT/PATCH/DELETE) must present a matching cookie and header.

import { csrfProtection, CsrfOptions, withMiddleware } from 'actor-ts/http';
const csrf = csrfProtection(
CsrfOptions.create().withSecret(process.env.CSRF_SECRET!),
);
const routes = withMiddleware(csrf, appRoutes);

On a safe request the token is set as a (non-HttpOnly) cookie and also forwarded to the handler, which reads it with readCsrfToken(req) and templates it into a form field or <meta> tag. The browser echoes it back in the X-CSRF-Token header (or a configured form field) on the next unsafe request.

Three separate things carry the defence, and it pays to be exact about which does what:

  • The HMAC rejects tokens this server never minted — garbage, a guess, a token from another deployment. It binds a token to the server secret and to nothing else, so a token minted for one client verifies for every other. It is not what stops cookie planting.
  • The __Host- cookie name is what stops planting, and it is the default. A browser refuses such a cookie unless it is Secure, Path=/ and carries no Domain, which locks it to exactly this host: a sibling subdomain cannot write it, and a plaintext origin cannot write it at all.
  • The Origin/Referer gate on unsafe methods rejects the cross-site request that would carry a planted pair in the first place.
Builder methodFieldDefault
withSecret(s)secretrequired, ≥ 16 bytes
withCookieName(n)cookieName'__Host-csrf-token'
withHeaderName(n)headerName'x-csrf-token'
withCookie(attrs)cookiePath=/, Secure, SameSite=Lax
withVerifyOrigin(flag?)verifyOrigintrue — also check Origin/Referer
withAllowedOrigins(...o)allowedOriginsextra accepted full origins
withExpectedScheme(s)expectedScheme'https' — or 'http' when the cookie is not Secure
withFormField(name)formFieldNameoff — read the token from a urlencoded body field too

The origin check compares whole origins — scheme, host and port — on both arms: the request’s own origin and every allowedOrigins entry. So https://app.example accepts neither http://app.example nor https://app.example:8443, and an Origin: null (an opaque origin, e.g. a sandboxed iframe) is always rejected.

A request tells you its host, in the Host header, but never its scheme — TLS may terminate at a proxy, and a forwarded scheme header is set by the client, so it isn’t trusted. expectedScheme supplies that missing half. It defaults to 'https'; csrfProtection reads 'http' instead when you explicitly turn off the Secure cookie attribute, since that already declares a plain-HTTP deployment:

const csrfOptions = CsrfOptions.create()
.withSecret(process.env.CSRF_SECRET!)
// Local dev over plain HTTP: a __Host- cookie cannot be set at all, and
// dropping Secure also switches the expected scheme to http.
.withCookieName('csrf-token')
.withCookie({ secure: false });

Entries of allowedOrigins must be full origins ('https://partner.example', not 'partner.example'); anything else throws an OptionsError when the middleware is built, because it could never match.

The lightweight alternative — requireSameOrigin

Section titled “The lightweight alternative — requireSameOrigin”

If you only need modern-browser protection, requireSameOrigin() rejects unsafe-method requests whose Origin/Referer isn’t the request’s own origin:

import { requireSameOrigin, withMiddleware } from 'actor-ts/http';
const routes = withMiddleware(requireSameOrigin(), appRoutes);

It takes the same allowedOrigins and expectedScheme (plus allowMissingOrigin), with the same 'https' default — a plain-HTTP site needs SameOriginOptions.create().withExpectedScheme('http'), as there is no cookie here to infer it from.

csrfProtection is the belt-and-suspenders option (it runs this check too); requireSameOrigin alone is lighter but relies on the browser sending a correct Origin/Referer.

  • CORS — cross-origin request handling.
  • Security — where CSRF sits in the stack.