Sessions and authorization
Protect cookie requests from CSRF
Use SameSite cookies, origin checks, and anti-CSRF tokens where needed for state-changing browser requests.
Browsers attach cookies automatically. That’s convenient, and it’s the root of a whole class of attacks. If a user is logged in to the Books API and visits a malicious page, that page can submit a form to POST /books/17/delete, and the browser adds the session cookie on its own.
This is cross-site request forgery, CSRF. Notice that the attacker never sees the response. They don’t need to. The request alone changed the data.
Three layers
No single control covers every flow, so we stack them.
SameSite on the cookie. We set SameSite=Lax in the cookie lesson, so the browser leaves the cookie out of most cross-site requests. Good defense in depth, but it doesn’t cover everything: top-level GET navigations still carry it under Lax, and older clients vary.
Origin checks. For sensitive browser requests, read the Origin header and reject anything that isn’t your own origin. Fetch Metadata headers such as Sec-Fetch-Site give you a second signal. Decide explicitly what happens when the header is missing, as it can be for older or non-browser clients. Don’t silently accept every absence.
Anti-CSRF tokens. The classic synchronizer token pattern puts a random value in the legitimate page and requires it back in the request:
<input type="hidden" name="csrfToken" value="session-bound-token">
The server compares it with a value tied to the session. A cross-site page can submit a form to your app, but the same-origin policy stops it from reading your page, so it can’t get the token.
Never change state on GET
A GET /books/17/delete link can be triggered by an <img> tag on any page. Reads are GET. Writes are POST, PUT, PATCH, or DELETE. No exceptions.
What CSRF tokens don’t fix
CSRF protection assumes the attacker’s code runs on another origin. If they’ve injected script into your page through XSS, that script reads the token from the form and acts as the user. Output escaping and a Content Security Policy belong in the same defense.
Every state transition, not just books
List every cookie-authenticated action that changes something. It’s longer than the Books routes: logout, password change, email change, passkey and MFA enrollment and removal, linking or unlinking a federated identity.
Login itself needs protection too, against login CSRF, where an attacker logs the victim into the attacker’s account. The OAuth callback is a special case: the transaction’s state value and the OIDC nonce protect it, not an ordinary form token. We’ll see those in the federated module.
Try this: write five tests against one state-changing route. Missing token, wrong token, foreign Origin, a valid same-origin request, and a GET to the same path. Only the fourth may change the database, and the fifth must return the resource without touching it.
Lesson completed