Skip to main content

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.com
  • https://dashboard.example.com:8443
  • http://dashboard.example.com

Production URL validation​

In 1.1.0, production configuration rejects:

  • application URLs and origins without HTTPS
  • localhost and *.localhost
  • 127.0.0.0/8
  • 0.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.

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
  • http versus https
  • missing origin header
  • malformed URL
  • CSRF token from another session
  • stale rotation token
  • safe versus unsafe methods
  • loopback and IPv4-mapped loopback configuration