By Rahul — Google Frontend Engineer
The Attack in Simple Terms
CSRF (Cross-Site Request Forgery) tricks your browser into making a request to a site where you are already logged in. The attacker does not need your password — they use the fact that your browser automatically sends cookies with every request.
How It Works
// You are logged into your-bank.com
// Attacker sends you a link to evil-site.com
// evil-site.com contains:
<img src="https://your-bank.com/transfer?to=attacker&amount=10000" />
// Your browser loads this image
// It sends the request to your-bank.com
// WITH your authentication cookies
// The bank thinks it is a legitimate request from youReal-World Example
In 2007, Gmail had a CSRF vulnerability. An attacker could create a page that, when visited by a Gmail user, would add email forwarding filters to forward all of the victim's email to the attacker.
Prevention Methods
1. CSRF Tokens (Most Common)
// Server generates a random token per session
// Includes it in every form as a hidden field
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="random-token-abc123" />
<input name="amount" />
<button>Transfer</button>
</form>
// Server validates the token on submission
// Attacker cannot guess this token2. SameSite Cookies
// The modern, easiest solution
Set-Cookie: session=abc123; SameSite=Strict
// SameSite=Strict: Cookie NEVER sent on cross-site requests
// SameSite=Lax: Cookie sent on top-level navigations (GET only)
// SameSite=None: Cookie always sent (must have Secure flag)Since Chrome 80 (2020), SameSite=Lax is the DEFAULT. This alone prevents most CSRF attacks.
3. Check Origin/Referer Headers
// Server checks that the request comes from your domain
app.post('/api/transfer', (req, res) => {
const origin = req.headers.origin || req.headers.referer;
if (!origin || !origin.startsWith('https://your-bank.com')) {
return res.status(403).json({ error: 'CSRF detected' });
}
// Process request
});4. Custom Headers
// Browsers prevent cross-origin requests from setting custom headers
// (unless CORS allows it)
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json'
},
body: JSON.stringify({ amount: 100 })
});
// Simple HTML forms cannot set custom headers
// So if the server requires X-Requested-With, form-based CSRF failsWhy SPA Frameworks Are Safer
React, Vue, and Angular apps typically use JSON APIs with custom headers (Authorization: Bearer token). Since tokens are in headers (not cookies), CSRF does not apply — the attacker cannot make the browser add the Authorization header automatically.
Best Practices
- Set
SameSite=LaxorStricton all cookies - Use CSRF tokens for any cookie-based authentication
- Prefer token-based auth (JWT in headers) for APIs
- Validate Origin header on the server
- Use POST for state-changing operations (never GET)
Summary
CSRF exploits the browser's automatic cookie sending. SameSite cookies are the easiest modern defense. CSRF tokens are the traditional approach. SPA frameworks with token-based auth are naturally resistant.