Requests, files, and servers
Prevent cross-site request forgery
Protect cookie-authenticated state changes with intentional methods, SameSite cookies, origin checks, and proven anti-CSRF tokens.
The browser attaches a user’s cookies to a request based on where it is going, not where it started. CSRF abuses that ambient authority: a hostile page triggers a request to your site, and the session cookie rides along automatically.
A signed-in user visits a hostile page containing an auto-submitted form to /account/email. The browser adds the session cookie even though the hostile page cannot read the response.
Layer the defenses
First, use non-GET methods for anything that changes state, and set SameSite on the session cookie so it is not sent on cross-site subrequests.
res.cookie('session', sessionId, { httpOnly: true, secure: true, sameSite: 'lax' })
Second, check request intent on the server. Sec-Fetch-Site (Fetch Metadata) or the Origin header let you reject cross-site writes.
function sameSiteWrite(req, res, next) {
const site = req.headers['sec-fetch-site']
if (site && site !== 'same-origin' && site !== 'none') {
return res.status(403).end() // cross-site state change: refuse
}
next()
}
Third, add a per-session anti-CSRF token the server issues and verifies on each write. The hostile page cannot read the token, so it cannot forge a valid request.
if (req.body.csrf !== req.session.csrf) return res.status(403).end()
SameSite=Lax blocks many cross-site form posts, but legacy clients or required cross-site flows may need another design. Validate request intent on the server too.
Because XSS can read a token and defeat these controls, prevent both.
Submit a real state-changing request from a second origin and capture the server decision and unchanged account state. Repeat without the CSRF token or trusted origin, and test the legitimate form still works.
Lesson completed