What is Cross-Site Request Forgery (CSRF)?
Cross-Site Request Forgery (CSRF) is an attack that tricks a user's browser into making an unintended request to a web application where the user is already authenticated. The browser automatically attaches cookies -- including session cookies -- to every request sent to a domain, regardless of which site initiated the request. CSRF exploits this fundamental behavior of the web platform.
Unlike cross-site scripting (XSS), where the attacker injects code that runs in the context of the victim's session, CSRF does not require injecting any code into the target application. The attacker never sees the response. They only need to trigger a state-changing request -- a bank transfer, a password change, an email forwarding rule -- and the browser does the rest.
How Browsers Handle Cookies
To understand CSRF, you need to understand how browsers handle cookies. When a server sets a cookie with Set-Cookie: session=abc123, the browser stores it and attaches it to every subsequent request to that domain. It does not matter whether the request was initiated by the user clicking a link on the target site, by a form on a completely different site, or by an image tag embedded in a malicious email.
This is the core of CSRF: the browser cannot distinguish between a request the user intended to make and one that was triggered by a malicious page. The session cookie is attached either way, and the server sees a fully authenticated request.
Attack Scenarios
CSRF attacks target state-changing operations -- actions that modify data on the server. They cannot be used to steal data because the attacker never sees the response. The most dangerous CSRF targets are operations with high-value side effects.
Money Transfers
A banking application with a transfer endpoint at POST /transfer that accepts to and amount parameters is a textbook CSRF target. The attacker embeds a hidden form on their page that auto-submits when the victim visits:
<form action="https://bank.com/transfer" method="POST" id="f">
<input type="hidden" name="to" value="attacker-account"/>
<input type="hidden" name="amount" value="5000"/>
</form>
<script>document.getElementById('f').submit();</script>
If the user is logged into bank.com in another tab, the browser sends the session cookie with the form submission. The bank's server sees a valid authenticated request and executes the transfer.
Password Changes
If a password change form does not require the current password (or relies solely on the session cookie for authentication), an attacker can force a password change. After the victim's password is changed, the attacker logs in with the new password and takes over the account entirely.
Email Forwarding Rules
This is particularly insidious. An attacker uses CSRF to add a forwarding rule to the victim's email account, routing a copy of all incoming mail to the attacker's address. Unlike a password change, the victim may not notice this for weeks or months, during which time the attacker silently collects every email -- including password reset links for other services.
Admin Actions
CSRF against admin accounts is especially dangerous. If an administrator visits a page with a CSRF payload, the attack can create new admin users, change application settings, exfiltrate database backups, or modify access controls. The attacker does not need admin credentials -- they only need the admin to visit a page while logged in.
GET vs POST CSRF
CSRF is possible with both GET and POST requests, but the mechanics differ.
GET-based CSRF
If a state-changing operation can be triggered via GET (which violates HTTP semantics -- GET should be safe and idempotent), the attack is trivial. The attacker can use an image tag, a script tag, or a simple link:
<img src="https://bank.com/transfer?to=attacker&amount=5000" width="0" height="0"/>
The browser loads the "image" by making a GET request to bank.com, sending cookies along. This works even in emails rendered in webmail clients, making GET-based CSRF extremely dangerous. This is why HTTP standards require that GET requests must never have side effects.
POST-based CSRF
Most state-changing operations use POST, which requires a form submission. The attacker creates a hidden form and auto-submits it with JavaScript. Cross-origin POST requests with simple content types (application/x-www-form-urlencoded, multipart/form-data, text/plain) do not trigger a CORS preflight, so the browser sends them freely -- cookies included.
Requests with non-simple content types like application/json trigger a CORS preflight OPTIONS request first. If the server does not respond with appropriate CORS headers, the browser blocks the request. This is an incidental defense, not an intentional one, and should not be relied upon as the sole CSRF protection.
Real-World CSRF Attacks
Netflix CSRF (2006)
In 2006, security researcher Jeremiah Grossman demonstrated a CSRF attack against Netflix that could add DVDs to a victim's queue, change the shipping address, and modify account credentials -- all by having the victim visit a malicious webpage while logged into Netflix. At the time, Netflix had no CSRF protections. The attack demonstrated that CSRF was not merely a theoretical concern but a practical threat against major web applications.
ING Direct CSRF (2008)
ING Direct, one of the largest online banks, was found to be vulnerable to CSRF that could transfer funds between a victim's accounts. The attack was more sophisticated: it first used CSRF to create a new payee, then initiated a transfer to the attacker-controlled payee. The multi-step nature of the attack showed that workflows requiring multiple requests are not inherently safe from CSRF.
Gmail Filter CSRF (2007)
A CSRF vulnerability in Gmail allowed attackers to create email filters on a victim's account. The attacker could set up a filter that automatically forwarded all emails matching certain criteria (or all emails) to an external address. Victims who visited a malicious site while logged into Gmail had their email silently forwarded without any visible indication.
Defense: Synchronizer Token Pattern
The most widely deployed CSRF defense is the synchronizer token pattern (also called anti-CSRF tokens). The server generates a cryptographically random token, associates it with the user's session, and embeds it in every form as a hidden field. When the form is submitted, the server verifies that the token matches.
The attacker cannot include the correct token in their forged request because they cannot read the target page's HTML from a cross-origin context. The same-origin policy prevents evil.com from reading the contents of pages served by bank.com, so the token value remains secret.
For this defense to work, several conditions must hold:
- The token must be cryptographically random and unpredictable
- The token must be tied to the user's session
- The server must reject requests with missing or incorrect tokens
- The token must not be transmitted in a cookie (which would be sent automatically, defeating the purpose)
Defense: Double-Submit Cookie
The double-submit cookie pattern is a stateless alternative. The server sets a random value in both a cookie and a request parameter (header or form field). On submission, the server checks that the cookie value matches the parameter value.
This works because an attacker can cause the browser to send a cookie but cannot read the cookie value from a different origin. They cannot set the matching request parameter without knowing the cookie's value. However, this approach is weaker than synchronizer tokens if the attacker can perform a subdomain takeover or cookie injection attack -- they could set or overwrite the CSRF cookie from a related subdomain.
Defense: SameSite Cookie Attribute
The SameSite cookie attribute, first shipped in Chrome 51 (2016) and now supported by all major browsers, gives servers direct control over when cookies are sent on cross-site requests. It has three values:
SameSite=Strict-- The cookie is never sent on cross-site requests. If you click a link to bank.com from evil.com, the cookie is not included. This provides the strongest CSRF protection but can break legitimate cross-site navigation (clicking a link to the site from an email, for instance, requires re-authentication).SameSite=Lax-- The cookie is sent on top-level navigation GET requests (clicking a link) but not on cross-site POST requests, embedded resources, or AJAX calls. This is the default in modern browsers and prevents most CSRF attacks while preserving a reasonable user experience.SameSite=None-- The cookie is sent on all cross-site requests (the legacy behavior). Requires theSecureflag, meaning the cookie is only sent over HTTPS.
Since Chrome 80 (February 2020), cookies without an explicit SameSite attribute default to Lax. This single change eliminated a large class of CSRF vulnerabilities across the web, protecting applications that had never implemented any CSRF defenses.
Defense: Origin and Referer Header Checking
When a browser sends a request, it includes an Origin header (on POST, PUT, DELETE, and CORS requests) and a Referer header (on most requests) indicating where the request came from. The server can reject requests where the origin does not match the expected domain.
For example, if bank.com receives a POST request with Origin: https://evil.com, it knows the request was initiated cross-site and can reject it. This is a useful defense-in-depth measure, but it has limitations:
- The
Refererheader can be suppressed by the referring page usingReferrer-Policy: no-referrer - Some privacy proxies and browser extensions strip these headers
- Edge cases exist with redirects, data URIs, and bookmarklets
For these reasons, origin checking is best used as a supplementary defense alongside tokens or SameSite cookies, not as the sole protection.
Defense: CORS and Preflight Requests
Cross-Origin Resource Sharing (CORS) is not a CSRF defense per se, but it intersects with CSRF in important ways. When JavaScript makes a cross-origin request with a non-simple content type (such as application/json), the browser sends a preflight OPTIONS request first. If the server does not respond with the appropriate Access-Control-Allow-Origin header, the browser blocks the actual request.
This means JSON APIs that require Content-Type: application/json are incidentally protected from cross-origin form submissions. However, simple form submissions (application/x-www-form-urlencoded) bypass CORS preflight entirely. An attacker can craft a form that submits to any URL with a simple content type, and the browser will send it -- cookies and all -- without any preflight check.
How SPAs and JWTs Changed the Landscape
Single-page applications (SPAs) that communicate with backend APIs via fetch() or XMLHttpRequest have a fundamentally different CSRF profile than traditional server-rendered applications.
Token-Based Authentication
Many modern SPAs use JSON Web Tokens (JWTs) stored in localStorage or sessionStorage instead of cookies. The token is sent in the Authorization: Bearer <token> header on each request. Since the token is not in a cookie, the browser does not attach it automatically to cross-origin requests. This makes the application immune to CSRF by default -- the attacker cannot forge the Authorization header from a cross-site page.
However, if JWTs are stored in cookies (which some frameworks do to simplify token management and enable server-side rendering), all the same CSRF risks apply. The storage location, not the token format, determines CSRF vulnerability.
SPA API Patterns
SPAs typically send Content-Type: application/json requests, which trigger CORS preflight. Combined with strict CORS configuration that only allows the application's own origin, this provides a strong layer of CSRF protection. An attacker's page at evil.com cannot make a fetch() call with Content-Type: application/json to api.bank.com without a successful preflight.
But developers must be careful: if the API also accepts application/x-www-form-urlencoded for backward compatibility, the CORS protection is bypassed for those endpoints.
OAuth 2.0 and CSRF
OAuth 2.0 flows are themselves susceptible to a form of CSRF. The authorization code redirect can be forged: an attacker initiates an OAuth flow with their own authorization code, then tricks the victim's browser into completing the callback. This binds the attacker's third-party account to the victim's session. The state parameter in OAuth 2.0 exists specifically to prevent this -- it functions as a CSRF token for the OAuth redirect.
Defense in Depth: Layered Approach
No single defense is sufficient on its own. A robust anti-CSRF strategy layers multiple protections:
- SameSite cookies as the baseline -- they stop most CSRF with zero application code changes
- CSRF tokens for state-changing endpoints -- defense against browsers that do not fully support SameSite, or against subdomain-based attacks
- Origin/Referer checking as a backstop -- catches cases where tokens might be accidentally omitted
- Re-authentication for high-value operations -- password changes, large transfers, and admin actions should require the user to confirm their identity, regardless of other protections
Common Implementation Mistakes
Even when developers implement CSRF protections, common mistakes undermine them:
- Token not validated on the server -- The form includes a CSRF token field, but the server never actually checks it. This is more common than you might expect, especially after refactoring.
- Token in a cookie only -- Putting the CSRF token in a cookie and checking for its presence defeats the purpose. The token must also appear in a non-cookie location (form field, custom header) that the attacker cannot forge.
- Predictable tokens -- Using sequential IDs, timestamps, or MD5 hashes of predictable values. CSRF tokens must be generated with a cryptographically secure random number generator.
- GET endpoints with side effects -- If any state change can be triggered via GET, no CSRF defense can protect it because image tags, script tags, and link preloads can all issue GET requests.
- CORS wildcard with credentials -- Setting
Access-Control-Allow-Origin: *withAccess-Control-Allow-Credentials: true(which browsers actually reject) or reflecting any origin with credentials allows any site to make authenticated requests. - Subdomain vulnerabilities -- If any subdomain is compromised (via XSS or a dangling DNS record), cookies scoped to the parent domain can be overwritten, potentially breaking double-submit cookie defenses.
CSRF in the Modern Web
The CSRF threat has evolved significantly. The combination of SameSite=Lax as the browser default, widespread adoption of token-based authentication in SPAs, and better framework support for anti-CSRF tokens has reduced the prevalence of CSRF vulnerabilities. Many modern web frameworks -- Django, Rails, Laravel, Spring Security, ASP.NET -- generate and validate CSRF tokens automatically for form submissions.
However, CSRF has not disappeared. Applications that need SameSite=None for legitimate cross-site functionality (embedded widgets, federated login, payment processors) remain vulnerable without additional defenses. Legacy applications running behind reverse proxies may not benefit from SameSite defaults. And as the web platform evolves, new cross-origin interaction patterns may introduce novel CSRF-like attacks.
Understanding CSRF remains essential for anyone working on web security. The attack exploits a fundamental design decision of the web platform -- that browsers automatically attach credentials to requests -- and defending against it requires understanding how that decision ripples through every layer of the stack, from cookie handling to CORS to TLS and authentication protocol design.
Try It: Explore the Network Infrastructure
CSRF protection depends on the security infrastructure of the web -- TLS for secure cookies, DNS for origin verification, and the network layer that carries it all. Explore how these systems work at the routing level: