Refresh a Double-Submit CSRF Token Safely
Issue a random CSRF token in a readable cookie and require it in a header, refreshing it on login and comparing with a constant-time check.
Task
Protect state-changing requests with a double-submit token that survives SPA navigation and refreshes on session change.
When to use this
A SPA keeps a stale CSRF token after login, so legitimate writes fail with 403 until the page is fully reloaded.
Prerequisites
- A same-site cookie strategy
- HTTPS so cookies can be marked Secure
Steps
1. Set a random CSRF token in a cookie that JavaScript can read.
Expected output: The client can attach the token to requests.
2. Require the same token in a request header for state-changing methods.
Expected output: An attacker on another origin cannot read the cookie to forge the header.
3. Compare the header and cookie values in constant time on the server.
Expected output: Comparisons do not leak timing information.
4. Rotate the token on login, logout, and privilege change.
curl -X POST https://api.example.com/transfer -H "X-CSRF-Token: $TOKEN"Expected output: A stolen pre-login token is useless afterward.
Troubleshooting
Problem
403 after login
Solution
Rotate the token on login and return the new value so the SPA updates its copy.
Problem
Token missing on cross-site forms
Solution
Double-submit requires same-site cookies; check SameSite and Secure flags before relying on it.
Limitations
- Double-submit relies on the browser not allowing cross-site reads of the cookie, which depends on correct SameSite settings.
- It is weaker than a synchronizer token tied to the session unless combined with strict cookie attributes.
Context
Protect state-changing requests with a double-submit token that survives SPA navigation and refreshes on session change. A SPA keeps a stale CSRF token after login, so legitimate writes fail with 403 until the page is fully reloaded.
Steps
- Set a random CSRF token in a cookie that JavaScript can read. Expected: The client can attach the token to requests.
- Require the same token in a request header for state-changing methods. Expected: An attacker on another origin cannot read the cookie to forge the header.
- Compare the header and cookie values in constant time on the server. Expected: Comparisons do not leak timing information.
- Rotate the token on login, logout, and privilege change. Expected: A stolen pre-login token is useless afterward.
app.use((req, res, next) => {
if (['POST','PUT','PATCH','DELETE'].includes(req.method)) {
if (!timingSafeEqual2(req.cookies.csrf, req.get('X-CSRF-Token'))) {
return res.status(403).json({ error: { code: 'csrf' } });
}
}
next();
});
Verify
- The client can attach the token to requests.
- An attacker on another origin cannot read the cookie to forge the header.
- Comparisons do not leak timing information.
- A stolen pre-login token is useless afterward.
Limits
- Double-submit relies on the browser not allowing cross-site reads of the cookie, which depends on correct SameSite settings.
- It is weaker than a synchronizer token tied to the session unless combined with strict cookie attributes.