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

Take this course offline

Get every free book, course edition, and software download.

Get the download library →