What Is CSRF?
Cross-Site Request Forgery (CSRF) exploits the fact that browsers automatically attach cookies to any request sent to the cookie's origin domain, regardless of which site initiated that request. If a user is logged into bank.example.com and their browser holds a valid session cookie, a malicious page on evil.example can silently trigger a request to bank.example.com/transfer, and the browser will attach the legitimate session cookie exactly as if the user had submitted the form themselves. The server sees a validly authenticated request and has no built-in way to know the user did not intend to send it, which is what makes CSRF fundamentally a confused-deputy problem rather than an authentication bypass.
Cricket analogy: It's like a fan's signed entry pass being usable by anyone who finds it lying around, letting a stranger walk into the players' area on the fan's identity without the fan ever realizing their pass was used.
Anatomy of a CSRF Attack
A typical CSRF attack targets state-changing actions — changing an email address, transferring money, deleting an account — rather than data reads, since the attacker cannot see the response, only trigger the side effect. The classic vector is an auto-submitting HTML form hosted on an attacker-controlled page: the victim only needs to visit that page while logged into the target site, and JavaScript submits the form on page load with no click required. GET-based CSRF is even simpler, since a single <img src="..."> tag pointing at a state-changing URL will fire automatically. Attacks succeed whenever the target endpoint relies solely on cookie-based session identification without any secondary proof that the request originated from the site's own UI.
Cricket analogy: State-changing-only targeting is like a scam that only bothers forging a request to change a player's contract terms, not to view the public scorecard, since forging a read gains the attacker nothing they can't already see.
<!-- Hosted on evil.example — victim just has to load this page while logged in -->
<body onload="document.forms[0].submit()">
<form action="https://bank.example.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker-account" />
<input type="hidden" name="amount" value="5000" />
</form>
</body>
<!-- The browser attaches bank.example.com's session cookie automatically -->CSRF is not the same as XSS. XSS runs attacker script in the context of the vulnerable site itself; CSRF runs from a completely different origin and merely relies on the browser's automatic cookie attachment. A site can be XSS-free and still be fully vulnerable to CSRF.
Preventing CSRF
The standard defense is the synchronizer token pattern: the server embeds a unique, unpredictable CSRF token in each form or page, and every state-changing request must include that token in the request body or a custom header; since evil.example cannot read bank.example.com's page to extract the token, it cannot forge a valid request. The double-submit cookie pattern is a stateless variant, where the token is set as a cookie and also required in the request body or header, and the server just checks they match. SameSite cookies (Lax or Strict) provide strong browser-level defense by refusing to send the cookie on cross-site requests in the first place, and modern frameworks default new cookies to SameSite=Lax. Best practice layers both: SameSite cookies as a baseline, plus explicit CSRF tokens for state-changing requests as defense in depth.
Cricket analogy: A CSRF token is like a one-time authentication code printed only on the official scorecard handed to the actual scorer, so a forged scorecard from outside the ground can never present the matching code.
// Express example using a CSRF token middleware
app.get('/transfer-form', (req, res) => {
res.render('transfer', { csrfToken: req.csrfToken() });
});
app.post('/transfer', csrfProtection, (req, res) => {
// csrfProtection middleware rejects the request if req.body._csrf
// does not match the token issued for this session
processTransfer(req.body);
res.redirect('/success');
});
// Cookie config that adds a browser-level layer of defense
res.cookie('session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'Strict',
});Framework defaults: Rails, Django, and Spring all ship built-in CSRF protection enabled by default for form submissions. Disabling it (common when building a pure JSON API) requires an explicit, deliberate decision — and that API should then rely on SameSite cookies plus custom header requirements instead, since browsers won't auto-attach cookies to cross-origin fetch requests that require custom headers.
- CSRF abuses the browser's automatic cookie attachment to forge state-changing requests using a victim's valid session.
- It targets state-changing actions (transfers, deletes, settings changes), not data reads.
- GET-based CSRF can fire from a single auto-loading image tag; POST-based CSRF typically uses an auto-submitting form.
- CSRF is distinct from XSS — it doesn't require injecting script into the vulnerable site at all.
- Synchronizer tokens (server-issued, per-session, embedded in forms) are the classic defense.
- SameSite=Lax/Strict cookies provide a strong browser-level baseline defense.
- Best practice layers SameSite cookies with explicit CSRF tokens for defense in depth.
Practice what you learned
1. What browser behavior does CSRF fundamentally exploit?
2. Why do CSRF attacks typically target state-changing actions rather than data reads?
3. How does the synchronizer token pattern prevent CSRF?
4. What does setting a cookie's SameSite attribute to Strict primarily achieve?
5. How does CSRF differ fundamentally from XSS?
Was this page helpful?
You May Also Like
Security Headers Explained
HTTP security headers let a server instruct the browser to enforce protective behaviors, such as blocking mixed content, refusing to be framed, or restricting which scripts may execute, directly at the browser level.
Clickjacking and Frame Protections
Clickjacking tricks a user into clicking something different from what they perceive, typically by overlaying an invisible iframe of a target site beneath deceptive content on an attacker's page.
Broken Access Control
Broken access control is the failure to properly enforce what authenticated users are allowed to do, letting attackers view or modify data and functions that should be off-limits.