CSRF and origin checks
Origin checks and CSRF protect different parts of the same boundary. Origin limits which web source may send the request; the CSRF token proves the request was prepared for the active session.
Exact-origin allowlist
security: {
allowedOrigins: [
"https://dashboard.example.com",
"https://admin.example.com",
],
// ...
}
An origin is normalized by protocol, hostname, and port. The path is not part of it. Do not use wildcards, substring matching, or endsWith() checks.
These are different origins:
https://dashboard.example.comhttps://dashboard.example.com:8443http://dashboard.example.com
Production URL validation
In 1.1.0, production configuration rejects:
- application URLs and origins without HTTPS
localhostand*.localhost127.0.0.0/80.0.0.0::and::1- IPv4-mapped IPv6 addresses that resolve to loopback
The same guard applies to configured Discord OAuth authorize and token endpoints. This prevents a production token from being sent to a local endpoint after an environment-variable mistake.
CSRF secret
csrfSecret must contain at least 32 bytes. Tokens are HMAC values derived from the session ID hash and a random nonce.
const token = gate.csrf.issue(session.idHash);
const valid = gate.csrf.verify(session.idHash, tokenFromHeader);
Do not place the secret in source control. Plan rotation because replacing a single key invalidates existing CSRF tokens.
Unsafe methods
POST, PUT, PATCH, and DELETE are unsafe. They require a session-bound token unless the action explicitly sets csrf: "disabled".
Do not modify data from a GET route. Cache and crawler behavior would otherwise create a write outside CSRF protection.
Header and cookie layout
Example client request:
await fetch("/api/guilds/123/settings", {
method: "PATCH",
credentials: "include",
headers: {
"content-type": "application/json",
"x-csrf-token": csrfToken,
"idempotency-key": crypto.randomUUID(),
},
body: JSON.stringify(payload),
});
Do not store the same CSRF value inside the session cookie. Keep the cookie HttpOnly and provide the token through a controlled bootstrap endpoint or HTML payload.
Proxy note
Origin validation should use the actual Origin header rather than Host, X-Forwarded-Host, or referrer. Verify that the reverse proxy preserves it and redirects HTTP to HTTPS correctly.
Tests
- allowed production origin
- same host with a different port
httpversushttps- missing origin header
- malformed URL
- CSRF token from another session
- stale rotation token
- safe versus unsafe methods
- loopback and IPv4-mapped loopback configuration