Back to docs
Recipe
CSRF Double-Submit Cookie
Stateless CSRF protection that works without server-side session storage. The client sends the token twice — once as a cookie, once in a request header — and the server compares them.
How it works
- Server generates a cryptographically random token on first visit.
- Token is set as a cookie (not
HttpOnly— the client must read it). - Client reads the cookie and sends the same token in a custom header (
X-CSRF-Token) on every mutating request. - Server compares the header value to the cookie value. If they match, the request is accepted.
Why it works
An attacker on evil.com cannot read or set cookies for your origin. They can trigger a cross-origin request, but they cannot attach the custom header because browsers forbid setting custom headers on cross-origin requests without a CORS preflight that explicitly allows it.
Server pseudocode
// On first page load
const token = crypto.randomBytes(32).toString('hex')
res.setHeader('Set-Cookie', `csrf=${token}; SameSite=Strict; Secure; Path=/`)
// On mutating request
const cookieToken = req.cookies.csrf
const headerToken = req.headers['x-csrf-token']
if (!cookieToken || cookieToken !== headerToken) {
return res.status(403).json({ error: 'CSRF validation failed' })
}Important notes
- Use
SameSite=StrictorLaxas a defense-in-depth layer. - Never make the cookie
HttpOnly— the client JavaScript must be able to read it. - Regenerate the token after login to prevent session fixation.
- Subdomains under your control can overwrite the cookie if you don't scope it tightly. Use the most specific path and domain possible.
Meridian note: This pattern is used internally by the Nimbus loader's challenge-response handshake. The same principle — compare two independently delivered values — underpins our license validation flow.