Defence in Depth
Each defence so far has gaps: tokens can be misimplemented, SameSite has the site-vs-origin and GET holes, double-submit has cookie injection. Defence in depth is the discipline of layering independent mechanisms so that an attacker must defeat all of them — and a single misconfiguration doesn't expose you. This chapter covers the supplementary defences and how to stack them.
Checking the Origin / Referer Header
On a cross-site request, the browser sets an Origin header (and usually Referer) identifying the page that initiated it — and the attacker's page cannot forge or remove these on a cross-site request (they're browser-controlled). So the server can simply check: did this state-changing request come from my own origin?
Origin header is usually present on state-changing cross-site requests but historically wasn't on every request type, and Referer can be stripped by privacy settings, proxies, or Referrer-Policy — so a strict "reject if absent" can break legitimate clients, while "allow if absent" opens a hole. (2) It does nothing for the same-site sibling problem (Chapter 7) — a request from blog.example.com may carry an allowed-looking origin. Treat Origin/Referer checks as a strong corroborating layer alongside tokens + SameSite, not a replacement. Match against an explicit allowlist, exact-string, never a loose substring.
Requiring a Custom Request Header
From Chapter 2: a cross-site attacker cannot set a custom header on a simple request without triggering a CORS preflight they can't pass, and an HTML form can't set headers at all. So requiring a header like X-Requested-With: XMLHttpRequest on state-changing endpoints means a classic form/img forgery simply can't produce a valid request.
Re-Authentication & Step-Up for Sensitive Actions
The strongest layer for the highest-value actions doesn't rely on request shape at all: require the user to prove intent directly — re-enter their password, confirm with a one-time code, or pass an MFA challenge — for operations like changing email/password, deleting an account, or making a payment.
This defeats CSRF by construction: even a perfectly forged request can't supply the current password or live MFA code, because the attacker doesn't know them (Chapter 3's "account takeover via change-email" is exactly what this stops). It's friction, so reserve it for genuinely sensitive operations — but for those, it's the most robust defence available.
The Layered Stack
A well-defended state-changing endpoint combines several independent checks — an attacker must beat every one:
What to Actually Ship
For most apps the pragmatic, strong baseline is: SameSite=Lax + anti-CSRF tokens (via a maintained library) + safe-GET discipline, with Origin checks as an easy extra layer and re-authentication on the handful of truly sensitive endpoints. You don't need every layer everywhere — you need enough independent ones that no single failure is catastrophic, weighted toward your highest-value actions.
Hands-On Exercises
Implement an Origin/Referer check middleware in Express that allows only your own origin on state-changing requests. Then explain two reasons it must not be your only CSRF defence (header absence handling, and the same-site sibling gap).
📄 View solutionExplain why requiring re-authentication (re-entering the password) on a "change email" endpoint defeats CSRF even if every other defence were somehow bypassed. Connect this back to the account-takeover chain from Chapter 3.
📄 View solutionA team proposes "defence in depth": session cookie, a second auth cookie, and a CSRF cookie all checked server-side. Explain why this is not real defence in depth, then design a genuinely independent 3-layer stack and state the distinct assumption each layer relies on.
📄 View solutionChapter 8 Quick Reference
- Defence in depth — layer independent mechanisms so no single failure exposes you
- Origin/Referer check — reject state-changing requests not from your allowlisted origin; browser-set, attacker can't forge cross-site
- Origin caveats: header may be absent (proxies/Referrer-Policy) and won't catch same-site siblings — a layer, not sole defence
- Custom required header — forms can't set headers; cross-site fetch triggers a preflight — blocks classic forgery (enforce strictly)
- Re-authentication / MFA for sensitive actions — defeats CSRF by construction (attacker lacks the password/OTP); reserve for high-value ops
- Strong stack: SameSite + token + Origin check + safe-GET + re-auth on sensitive endpoints
- Layers must fail for different reasons — five cookie-trusting checks aren't depth; and nothing survives XSS
- Ship baseline: SameSite=Lax + tokens (library) + safe-GET, plus Origin checks and re-auth where it matters
- Next chapter: CSRF in modern apps — SPAs, JSON APIs, bearer vs cookie auth, framework built-ins