Cross-site request forgery happens when another site causes a logged-in user's browser to send a request to your app, and the browser attaches the session cookie automatically. Without a defense, that forged request runs with the user's privileges. The fix is two layered pieces: set sameSite on your session cookie, and require an unguessable CSRF token on state-changing requests.
Why it's a problem
Because the browser sends your cookie on any request to your domain, an attacker does not need the user's credentials, only a page the user visits while logged in. A hidden form or image request can change an email, transfer a balance, or delete data as the victim. Any cookie-authenticated POST, PUT, PATCH, or DELETE without a token is a candidate.
The pattern
// cookie session, no token check: a cross-site form can trigger this
app.post("/account/email", requireLogin, (req, res) => {
updateEmail(req.user.id, req.body.email);
res.json({ ok: true });
});The fix
import csrf from "csurf";
const csrfProtection = csrf({ cookie: false });
// session cookie should also be sameSite: 'lax' or 'strict'
app.post("/account/email", requireLogin, csrfProtection, (req, res) => {
// request is rejected unless it carries the token issued to this session
updateEmail(req.user.id, req.body.email);
res.json({ ok: true });
});Why AI tools write this
CSRF is invisible in the happy path an assistant is building: the form works, the update succeeds, nothing looks wrong. The defense requires wiring a token through the template and the request, which adds steps that are not needed to make the feature function, so the assistant ships the working route without it.
The quick fix
- Set sameSite: 'lax' or 'strict' on the session cookie as the first layer.
- Require a per-session CSRF token on every state-changing request and verify it server-side.
- Prefer token-in-header for APIs called by your own frontend.
- Exempt only endpoints that use a non-cookie auth scheme like a bearer token.
Common questions
Does sameSite: 'lax' alone fully protect against CSRF?
For most cross-site POST attacks, yes. But lax still allows cross-site GET requests, some top-level navigations can carry cookies, and older browsers have partial support. Use sameSite as the first layer plus a token as the second.
Is a CSRF token needed for REST APIs that use bearer token authentication?
No. A bearer token in an Authorization header is not automatically sent by the browser like a cookie, so cross-site forms cannot forge it. CSRF protection is only needed for cookie-based sessions.
What is the double-submit cookie pattern?
A CSRF token is set as a readable cookie and also sent as a request header. The server verifies both values match. An attacker from another origin cannot read the cookie value to forge the header, which is the protection.