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
| Mitigation | Mechanism | Caveat |
|---|---|---|
| SameSite=Lax | Cookies omitted on cross-site requests, except top-level GET navigations | Does not protect state-changing GET endpoints |
| SameSite=Strict | Cookies never sent on cross-site requests (even links from email/search) | Hurts user experience when clicking external links |
| Anti-CSRF Tokens | Server validates custom token submitted in the request body/headers | Adds state verification overhead to form submissions |
| Origin / Referer Checks | Server inspects request headers to verify origin matches target | Can be stripped by corporate proxies or browser privacy settings |
[!IMPORTANT] Never design API endpoints where
GETrequests 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
Secureflag (HTTPS only).
Anti-CSRF Token: Double-Submit Cookie Pattern
Used in stateless SPAs to avoid storing tokens in session state:
- The server generates a cryptographically secure random token.
- The server sets this token as a cookie (e.g.,
csrf-token) withHttpOnly: falseso JavaScript can read it. - 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). - 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
Express.js Double-Submit Cookie Middleware
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?