CSRF Basics for SPAs
Cross-site request forgery against cookie sessions: SameSite, CSRF tokens, and SPA fetch patterns.
- security
- csrf
- samesite
- cookies
- spa
CSRF tricks a victim’s browser into sending an authenticated request to your site from another site’s page. It exploits the fact that browsers attach cookies on cross-site requests under certain conditions. SPAs that use cookie sessions still need a CSRF story; SPAs that put bearer tokens only in memory have a different (XSS) threat profile.
Docs: OWASP CSRF, MDN SameSite, Fetch credentials.
Classic attack
- User logged into
bank.example(session cookie). - User visits
evil.example. - Page triggers
POST https://bank.example/transferwith cookie attached. - Bank accepts because session is valid.
<!-- evil page -->
<form action="https://bank.example/transfer" method="POST">
<input name="to" value="attacker" />
<input name="amount" value="1000" />
</form>
<script>document.forms[0].submit();</script>
SameSite cookies (first line)
Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Lax
| Value | Cross-site behavior (simplified) |
|---|---|
Strict |
Cookie not on cross-site requests |
Lax |
Sent on top-level GET navigations; not on most cross-site POSTs |
None |
Sent cross-site; requires Secure |
Lax stops many classic POSTs CSRF cases but not all flows (e.g. some GET side effects — never use GET for state change).
CSRF tokens (defense in depth)
# bootstrap
Set-Cookie: csrf=random; Secure; SameSite=Strict; Path=/
# double-submit or synchronizer pattern
await fetch('/api/transfer', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCsrfFromCookieOrMeta(),
},
body: JSON.stringify({ to, amount }),
});
Server verifies header/body token matches session. Cross-origin sites can’t read the token thanks to SOP (unless XSS).
Custom headers and preflight
Requiring X-Requested-With or CSRF headers forces CORS preflight on cross-origin; attacker origins won’t pass CORS. Same-site SPA + API on another subdomain still need careful CORS allowlists — preflight.
Bearer tokens in Authorization
If the token is not auto-attached by the browser (must set header from JS memory), classic cookie CSRF doesn’t apply. XSS steals the token instead — different control set.
Checklist
- No state-changing GETs.
SameSite=LaxorStricton session cookies.- CSRF token or equivalent for cookie-authenticated mutations.
- Tight CORS.
- Prefer re-auth for high-risk actions.
Interview out-loud
“CSRF uses the browser’s automatic cookie sending from a malicious site. SameSite=Lax/Strict blocks most cases; CSRF tokens defend the rest for cookie sessions. Bearer headers avoid cookie CSRF but raise XSS stakes.”
Reviewer prompts
- What is the asset (session, PII, money movement)?
- What is the attacker capability (web, XSS, network, dependency)?
- Which control fails closed if misconfigured?
- Is the server still enforcing authz?
- Any new third party, iframe, or URL sink?
Residual risk note
Browser controls reduce likelihood and impact; they do not eliminate bugs. Prefer defense in depth: safe defaults in code, strict headers, and monitoring (CSP reports, auth anomaly alerts). When product pressure weakens a control, write down the accepted risk and a revisit date.
Related
Further depth
Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.
Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.
Further reading
Related guides
- Frontend Security Threat ModelThreat-model SPAs: assets, attackers, XSS/CSRF/supply-chain, and where frontend controls actually land.
- Secure Cookie FlagsHttpOnly, Secure, SameSite, Path, Domain, and Priority: set session cookies so XSS and CSRF have less room.
- Auth Session UX SecuritySession UX that doesn’t weaken security: login states, logout everywhere, idle timeouts, and step-up auth.
- Privacy and Fingerprinting BasicsWhat browser fingerprinting collects, FE APIs that leak entropy, and privacy-preserving product defaults.
- Clickjacking and X-Frame-OptionsStop UI redress attacks with frame-ancestors CSP and X-Frame-Options; know when embedding is intentional.