CSRF Protection

Learn how Wristband SDKs generate and validate CSRF tokens.

Cross-site request forgery (CSRF) is an attack where a malicious site causes a user's browser to send a request to your application. Since the browser includes the session cookie automatically, the request looks like it came from the user. CSRF protection stops these requests by requiring a token that only your own frontend can read.

Backend server auth SDKs implement CSRF protection using the Synchronizer Token Pattern. The SDK stores a CSRF token in the session, and your frontend must send the same token in a request header. The auth middleware rejects any request where the two don't match.

CSRF protection is disabled by default. The session cookie's default SameSite=Lax setting already blocks most cross-site requests, so CSRF protection is mainly needed when your session cookie uses SameSite=None, or as an additional layer of defense. Most SDKs support it while some SDKs rely on their framework's own CSRF protection instead.

Enabling CSRF Protection

When CSRF protection is enabled, the auth middleware validates the CSRF token on every request that uses the SESSION strategy.

For example, using the Express Auth SDK:

// src/wristband.ts
const sessionOptions = {
  secrets: process.env.SESSION_SECRET!,
  enableCsrfProtection: true,
};
 
export function wristbandSession() {
  return createWristbandSession(sessionOptions);
}
 
export const requireWristbandAuth = wristbandAuth.createAuthMiddleware({
  authStrategies: ['SESSION'],
  sessionConfig: {
    sessionOptions,
  },
});

How CSRF Protection Works

When CSRF protection is enabled, the SDK keeps the CSRF token in two places:

  • The session cookie: The token is stored in the encrypted session data, where only your backend server can read it.
  • The CSRF cookie: The same token is stored in a separate, unencrypted cookie that isn't HttpOnly, so JavaScript running in your frontend can read it.

On each request to a protected route, your frontend reads the token from the CSRF cookie and sends it in the CSRF header. The auth middleware compares the header value against the token in the session. A malicious site can make the browser send your cookies, but the browser's same-origin policy prevents it from reading them, so it can't put the right token in the header.

Generating the CSRF Token

The session generates the CSRF token, not the auth middleware. Turn on CSRF protection in your session options to have the session generate it.

For example, using the Express Auth SDK:

// src/wristband.ts
import { createWristbandSession } from '@wristband/express-auth/session';
 
const sessionOptions = {
  secrets: process.env.SESSION_SECRET!,
  enableCsrfProtection: true,
};
 
export function wristbandSession() {
  return createWristbandSession(sessionOptions);
}

Once CSRF protection is on, the SDK manages the token for the life of the session:

  • When the session is first saved: The SDK generates a cryptographically random CSRF token. This typically happens when your Callback Endpoint creates the session after login.
  • Each time the session is saved: The token stays the same. The SDK writes the CSRF cookie again alongside the session cookie, so both cookies expire together.
  • When the session is destroyed: The SDK deletes the CSRF cookie along with the session cookie.

For example, the CSRF cookie set on the response that saves the session:

Set-Cookie: CSRF-TOKEN=8f2b4c6e9a1d3f5b7c0e2a4d6f8b1c3e5a7d9f0b2c4e6a8d1f3b5c7e9a0d2f4b; Path=/; Max-Age=3600; Secure; SameSite=Lax

Validating the CSRF Token

The auth middleware validates the CSRF token when CSRF protection is turned on in its configuration. You can also set the request header it reads the token from.

For example, using the Express Auth SDK:

// src/wristband.ts
export const requireWristbandAuth = wristbandAuth.createAuthMiddleware({
  authStrategies: ['SESSION'],
  sessionConfig: {
    sessionOptions: {
      secrets: process.env.SESSION_SECRET!,
      enableCsrfProtection: true,
    },
    // Optional. Defaults to X-CSRF-TOKEN.
    csrfTokenHeaderName: 'X-CSRF-TOKEN',
  },
});

On every request that uses the SESSION strategy, the middleware compares the CSRF header against the token in the session. It does this before it refreshes tokens or saves the session.

If the header is missing, or its value doesn't match the token in the session, the middleware rejects the request with a 403 Forbidden response.

Some SDKs only validate CSRF tokens on API routes and not on server-rendered pages (e.g. Next.js). SDKs that rely on their framework's CSRF protection follow the framework's rules for which requests are validated (e.g. Django).

ℹ️

No CSRF Validation for JWTs

The JWT strategy doesn't validate CSRF tokens since requests authenticated with a Bearer token don't rely on cookies.

Sending the CSRF Token From Your Frontend

Your frontend must read the CSRF token from the CSRF cookie and send it in the CSRF header on every request to a protected route. For example:

POST https://customer01.yourapp.io/api/orders
Cookie: session=...; CSRF-TOKEN=8f2b4c6e9a1d3f5b...
X-CSRF-TOKEN: 8f2b4c6e9a1d3f5b...

Wristband frontend SDKs send the CSRF token automatically on their own requests to your Session and Token Endpoints. For your own requests, read the token from the cookie and add the header yourself.

For example, using the React Client Auth SDK:

import { getCsrfToken } from '@wristband/react-client-auth';
 
const response = await fetch('/api/orders', {
  method: 'POST',
  credentials: 'include',
  headers: { 'X-CSRF-TOKEN': getCsrfToken() ?? '' },
});

The cookie name and header name your frontend uses must match the ones your backend server is configured with.

Frontends on a Different Subdomain

The CSRF cookie can only be read by JavaScript running on a domain the cookie is sent to. If your frontend runs on a different subdomain than your backend server (e.g. app.yourapp.io and api.yourapp.io), set the CSRF cookie domain to the parent domain (e.g. .yourapp.io) so your frontend can read it.

Only the CSRF cookie needs to be shared this way. The session cookie should stay scoped to your backend server's domain.

For example, using the Express Auth SDK:

const sessionOptions = {
  secrets: process.env.SESSION_SECRET!,
  enableCsrfProtection: true,
  csrfCookieDomain: '.yourapp.io',
};

CSRF Configuration

CSRF protection is configured in two places: the session generates the token, and the auth middleware validates it.

Session Configuration

The following session configuration controls how the CSRF token and CSRF cookie are generated:

ConfigurationDefaultDescription
CSRF protectionOffWhether the session generates a CSRF token and sets the CSRF cookie.
CSRF cookie nameCSRF-TOKENThe name of the CSRF cookie.
CSRF cookie domainThe session cookie domainThe domain of the CSRF cookie.

Auth Middleware Configuration

The following auth middleware configuration controls how the CSRF token is validated:

ConfigurationDefaultDescription
CSRF protectionOffWhether the auth middleware validates CSRF tokens.
CSRF header nameX-CSRF-TOKENThe request header the auth middleware reads the CSRF token from.

Configuration names, and where each one is set, vary by SDK. HTTP header names aren't case-sensitive, so X-CSRF-TOKEN and x-csrf-token refer to the same header.



Did this page help you?