iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Synchronizer Token

The synchronizer-token pattern stops CSRF by binding requests to a server-issued, per-session token. The server issues it; the client echoes it in a header or hidden field; the server verifies on every state-changing request.

Express + Laravel + manual middleware

EXAMPLE
// 1) Express — issue + verify on /api routes
import crypto from 'crypto';
import session from 'express-session';

app.use(session({
    secret: process.env.SESSION_SECRET,
    cookie: { httpOnly: true, secure: true, sameSite: 'lax' },
}));

// Generate per-session token, expose via /csrf
app.get('/csrf', (req, res) => {
    req.session.csrf ??= crypto.randomBytes(32).toString('base64url');
    res.json({ token: req.session.csrf });
});

// Middleware — require valid token on mutating methods
app.use((req, res, next) => {
    if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
    const sent = req.headers['x-csrf-token'] ?? req.body?.csrf;
    if (!sent || !req.session.csrf || !crypto.timingSafeEqual(
        Buffer.from(sent),
        Buffer.from(req.session.csrf),
    )) {
        return res.status(403).json({ error: 'csrf' });
    }
    next();
});

// Client (fetch) — bootstrap the token, send it on writes
const { token } = await fetch('/csrf', { credentials: 'include' }).then(r => r.json());
await fetch('/api/posts', {
    method:      'POST',
    credentials: 'include',
    headers:     { 'content-type': 'application/json', 'x-csrf-token': token },
    body:        JSON.stringify({ title: 'Hi' }),
});

// 2) Double-submit cookie pattern — stateless variant
app.use((req, res, next) => {
    if (!req.cookies['csrf']) {
        const t = crypto.randomBytes(32).toString('base64url');
        res.cookie('csrf', t, { sameSite: 'lax', secure: true });
    }
    if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
    if (req.cookies['csrf'] !== req.headers['x-csrf-token']) {
        return res.status(403).json({ error: 'csrf' });
    }
    next();
});

# 3) Laravel — CSRF is built in
# routes/web.php uses the VerifyCsrfToken middleware automatically.
# Blade form:
# <form method="POST" action="/posts">
#     @csrf
#     ...
# </form>

# Or in JS:
# const token = document.querySelector('meta[name="csrf-token"]').content;
# fetch('/posts', { method: 'POST', headers: { 'X-CSRF-TOKEN': token } });

# Layout:
# <meta name="csrf-token" content="{{ csrf_token() }}">

# 4) Django — also built in
# {% csrf_token %}                          {# template tag in forms #}
# CsrfViewMiddleware                        {# already in MIDDLEWARE #}
# Cookie:  csrftoken=...                     {# read in JS, send in header #}
# Header:  X-CSRFToken: <value>

# 5) Rotating the token
#   • After login/logout — invalidate the old session token
#   • If risk-based — on suspicious activity, re-issue
#   • Always use a single token per session — not per request (UX)

# 6) Don't do these
# - Tokens in URLs              (leak via Referer + logs)
# - Plain-text comparison       (use timingSafeEqual)
# - GET-with-side-effects       (always require POST/PUT/DELETE for state changes)
# - 'csrftoken' in localStorage (XSS reads it; SameSite + httpOnly cookie wins)

# 7) Defence-in-depth — combine with SameSite
# SameSite=Lax on the session cookie kills cross-site POSTs by default.
# CSRF tokens then cover the remaining edge cases (subdomain trust, legacy clients).

Why it matters

Synchronizer tokens + SameSite=Lax cookies + strict CORS are the modern triple-defence. Each layer covers what the others miss; bypassing all three at once requires a separate XSS vulnerability.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Server issues a random token per session.
<form method="post" action="/transfer">
    <input type="hidden" name="csrf" value="{{ csrf_token }}">
    …
</form>
// Server
if (req.body.csrf !== req.session.csrf) return res.status(403).end();
Try it Yourself »

Exercise

Token placement in a classic form.

<input type=" " name="csrf" value="…">

Test yourself

Q1. The synchronizer pattern stores the token…
Q2. The form submits the token in…
Q3. On mismatch, the server should…

Discussion

Loading…