THN Interview Prep

CSRF & Session Cookies

Cross-Site Request Forgery (CSRF) exploits the browser's default behavior of automatically attaching credentials (cookies, HTTP basic auth) to requests, even when those requests originate from a malicious third-party site.


Core details

MitigationMechanismCaveat
SameSite=LaxCookies omitted on cross-site requests, except top-level GET navigationsDoes not protect state-changing GET endpoints
SameSite=StrictCookies never sent on cross-site requests (even links from email/search)Hurts user experience when clicking external links
Anti-CSRF TokensServer validates custom token submitted in the request body/headersAdds state verification overhead to form submissions
Origin / Referer ChecksServer inspects request headers to verify origin matches targetCan be stripped by corporate proxies or browser privacy settings

[!IMPORTANT] Never design API endpoints where GET requests perform state changes (writes, deletes, or mutations). SameSite Lax cookies are sent on top-level cross-origin link clicks, which will trigger the mutation if it relies only on Lax cookies for AuthN.


Understanding

SameSite Cookies

The browser standard introduced SameSite to protect against ambient authority exploits:

  • SameSite=Lax (Recommended Default): Cookies are not sent on cross-origin requests (e.g., standard image loads or Ajax posts) but are sent when a user navigates to the origin (e.g., clicking a link).
  • SameSite=None: Cookies are sent on all requests. Must be accompanied by the Secure flag (HTTPS only).

Used in stateless SPAs to avoid storing tokens in session state:

  1. The server generates a cryptographically secure random token.
  2. The server sets this token as a cookie (e.g., csrf-token) with HttpOnly: false so JavaScript can read it.
  3. For any modifying request (POST, PUT, DELETE), the client reads the cookie value and inserts it into a custom request header (e.g., X-CSRF-Token).
  4. The server compares the header value against the cookie value. Because a cross-origin script cannot read your cookies (due to Same-Origin Policy), a CSRF attacker cannot populate this header.

Senior understanding

import express, { Request, Response, NextFunction } from 'express';
import crypto from 'node:crypto';
import cookieParser from 'cookie-parser';

const app = express();
app.use(cookieParser('cookie-signing-secret'));
app.use(express.json());

// Middleware to verify CSRF token
function verifyCsrfToken(req: Request, res: Response, next: NextFunction) {
  const method = req.method;
  if (['GET', 'HEAD', 'OPTIONS'].includes(method)) {
    return next();
  }

  const cookieToken = req.signedCookies['csrf_token'];
  const headerToken = req.headers['x-csrf-token'];

  if (!cookieToken || !headerToken || cookieToken !== headerToken) {
    return res.status(403).json({ error: 'CSRF token mismatch or missing' });
  }

  next();
}

// Route to generate and set the CSRF cookie for the client
app.get('/api/csrf-init', (req: Request, res: Response) => {
  const csrfToken = crypto.randomBytes(32).toString('hex');
  
  res.cookie('csrf_token', csrfToken, {
    signed: true,
    secure: true,
    httpOnly: false, // Must be readable by client JS to copy into custom headers
    sameSite: 'lax',
  });

  res.json({ csrfToken });
});

Diagram

Loading diagram…

See also

Mark this page when you finish learning it.

Spotted something unclear or wrong on this page?

On this page