Stateless CSRF Protection for Express (Double Submit Cookie)

4.0.3 · active · verified Sun Apr 19

csrf-csrf is a utility package designed to provide stateless Cross-Site Request Forgery (CSRF) protection for Express applications, implementing the Double Submit Cookie Pattern. Currently at version 4.0.3, it offers a robust alternative to the deprecated `csurf` library, aiming for a simpler and more explicit configuration. Unlike session-based CSRF protection mechanisms like `csrf-sync` (which uses the Synchronizer Token Pattern), `csrf-csrf` is suited for stateless architectures, making it a distinct choice for specific application designs. The library ships with comprehensive TypeScript types (requiring TypeScript >= 3.8) and emphasizes clear implementation guidance to prevent common misconfigurations that can render CSRF protection ineffective. Development is active, with a recent major version release bringing breaking changes and improvements, and it explicitly recommends consulting upgrade guides for migration.

Common errors

Warnings

Install

Imports

Quickstart

Demonstrates a basic Express server configuring `csrf-csrf` with `cookie-parser`, exposing a route to fetch a CSRF token, and protecting a POST endpoint. It highlights proper middleware ordering and token retrieval.

import express from 'express';
import cookieParser from 'cookie-parser';
import { doubleCsrf } from 'csrf-csrf';
import { Request, Response } from 'express';

const app = express();
const port = 3000;

// Initialize doubleCsrf with a secret.
// For production, use a strong, securely generated secret from environment variables.
const {
  doubleCsrfProtection,
  generateToken,
} = doubleCsrf({
  secret: process.env.CSRF_SECRET ?? 'your-very-strong-and-secret-key-that-you-must-change-in-prod-12345',
  cookieName: 'x-csrf-token',
  cookieOptions: {
    httpOnly: true,
    sameSite: 'lax', // 'Lax' or 'Strict' is recommended for CSRF protection
    secure: process.env.NODE_ENV === 'production',
  },
  getTokenFromRequest: (req: Request) => {
    // IMPORTANT: Be explicit here. Do not use fallthroughs (||, ??).
    // Prioritize header, then body. If not found, return an empty string.
    if (req.headers['x-csrf-token']) {
      return req.headers['x-csrf-token'] as string;
    }
    if (req.body && req.body._csrf) {
      return req.body._csrf;
    }
    return '';
  },
});

app.use(express.json()); // For parsing application/json
app.use(express.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded
app.use(cookieParser(process.env.COOKIE_SECRET ?? 'your-cookie-secret-for-signing-if-needed')); // Use a separate secret for cookie-parser

// CSRF protection middleware must be after cookie-parser
app.use(doubleCsrfProtection);

// Route to get a new CSRF token for the frontend
app.get('/csrf-token', (req: Request, res: Response) => {
  const csrfToken = generateToken(req);
  res.json({ csrfToken });
});

// Example protected POST route
app.post('/submit-data', (req: Request, res: Response) => {
  // If the doubleCsrfProtection middleware didn't throw an error,
  // the request is considered valid.
  res.json({ message: 'Data submitted successfully!', received: req.body });
});

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

view raw JSON →