What is CSRF and how do you defend against it?
Take a moment.
Say your answer out loud or write it down first — retrieval is what makes it stick. Reveal when you're ready.
Short answer
CSRF tricks a logged-in user's browser into issuing an unwanted state-changing request (a hidden form POST to your bank) riding their real cookies. Defend with SameSite cookies (Lax/Strict) as the baseline, anti-CSRF tokens for cookie-authenticated mutations, and Origin/Referer checks — plus requiring non-GET methods for anything that changes state.
Detailed explanation
The attack exploits cookie auto-attach: while you are logged into bank.com, evil.com serves <form action="https://bank.com/transfer" method="POST"> auto-submitted on load — your browser happily attaches your session cookie, and the bank sees a legitimate authenticated transfer. GET-based actions are even easier (an <img src="…/delete?id=7"> fires on page view, which is why state changes must never be GET). Defenses stack. SameSite=Lax (now the browser default) blocks cookie sending on cross-site POSTs and subresource loads — killing classic CSRF with zero code — but top-level GET navigation still sends, so Lax alone leaves GET endpoints exposed (don't have any). Anti-CSRF tokens close the rest: the server embeds an unguessable per-session token in forms (or a double-submit cookie), and the attacker, unable to read your pages cross-origin, cannot include it — the request fails validation. Custom headers (X-Requested-With) or fetch with same-origin mode work the same way for APIs: simple cross-site forms cannot set them, triggering a CORS preflight the attacker's origin fails. Server-side Origin/Referer allowlisting adds a cheap extra check. Note the complement: token-in-header auth (Authorization: Bearer) is inherently CSRF-proof since browsers never auto-attach it — CSRF is specifically a cookie-authentication problem, which is why SameSite exists.
Example
<!-- The attack: invisible auto-submitted form on evil.com -->
<form action="https://bank.com/transfer" method="POST" id="x">
<input type="hidden" name="to" value="attacker" />
<input type="hidden" name="amount" value="1000" />
</form>
<script>document.getElementById('x').submit();</script>
// Defense layers (server side):
// 1. Set-Cookie: sessionId=…; SameSite=Lax ← blocks this POST's cookies
// 2. Require a per-session token the attacker can't read:
// if (req.body.csrfToken !== req.session.csrfToken) return 403;
// 3. Never mutate state on GET.Interview tip
Walk the bank-transfer story in two sentences, then stack the defenses (SameSite → tokens → no-GET-mutations). Noting that bearer-token APIs are inherently immune shows you see the whole auth picture.
Common mistakes
- Relying only on checking Referer, which browsers and extensions can suppress.
- Putting state-changing actions behind GET routes.
- Assuming CORS alone prevents CSRF — simple form POSTs never preflight.