CSRF Attack Prevention: The Complete Developer Guide
Cross-site request forgery (CSRF attack prevention) is one of the most misunderstood yet dangerous vulnerabilities in modern web development. A CSRF attack forces an authenticated user's browser to send a forged HTTP request to your application — executing actions the user never intended. In 2024, CSRF still appears in real-world breaches despite being well-documented and entirely preventable.
Scan your site for CSRF misconfigurations instantly with [ZeriFlow](https://zeriflow.com) — 80+ security checks, completely free.
What Is a CSRF Attack and How Does It Work?
CSRF exploits the trust a web application places in the user's browser. When a user is authenticated (their session cookie is valid), any request originating from their browser — even from a malicious third-party page — carries that session cookie automatically.
Here's a classic attack scenario:
- 1Alice logs into her bank at
bank.com. A session cookie is set. - 2Alice visits a malicious site,
evil.com. - 3
evil.comcontains a hidden form that POSTs tobank.com/transfer?amount=5000&to=attacker. - 4Alice's browser automatically attaches her
bank.comsession cookie. - 5The bank sees a valid authenticated request and processes the transfer.
Alice never clicked anything. She never saw a form. The attack is entirely transparent.
Why Cookies Are the Root Cause
Browsers follow the same-origin policy for JavaScript, but cookies are attached to requests regardless of origin. This fundamental browser behavior is what CSRF exploits. The attack doesn't steal the cookie — it just abuses the fact that the browser sends it automatically.
CSRF Tokens: The Primary Defense
A synchronizer token pattern (CSRF token) is the most robust defense. The server generates a cryptographically random, per-session or per-request token, embeds it in every form, and validates it on submission.
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="a9f3c2e1b8d74f6a...">
<!-- other fields -->
</form>On the server side:
# Django example
from django.middleware.csrf import get_token
def transfer_view(request):
if request.method == 'POST':
token = request.POST.get('csrf_token')
if not validate_csrf_token(request, token):
return HttpResponseForbidden('CSRF validation failed')
# process transferKey Requirements for CSRF Tokens
- Cryptographically random: Use
secrets.token_hex(32)in Python,crypto.randomBytes(32)in Node.js. - Per-session or per-request: Per-request tokens are stronger but break the back button.
- Tied to the session: Store the token server-side, associated with the session ID.
- Transmitted out-of-band from cookies: Use a form field, a custom HTTP header, or a meta tag — never a cookie alone.
SameSite Cookie Attribute: A Powerful Layer of Defense
The SameSite cookie attribute tells the browser when to send cookies with cross-site requests. It's one of the most effective CSRF mitigations available today.
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnlySameSite Values Explained
| Value | Behavior | CSRF Protection |
|---|---|---|
Strict | Cookie never sent on cross-site requests | Full protection |
Lax | Cookie sent on top-level GET navigations only | Partial protection |
None | Cookie always sent (requires Secure) | No protection |
`SameSite=Strict` is the gold standard. It means your session cookie will not be sent at all when the request originates from a different site — completely neutralizing CSRF.
`SameSite=Lax` (the browser default since ~2020) still protects against POST-based CSRF attacks but allows session cookies on top-level GET navigations (clicking a link). This is a reasonable default for most apps.
Warning: SameSite=None is required for legitimate cross-site embeds (payment widgets, OAuth flows) but removes CSRF protection for those cookies.
Checking Your SameSite Configuration
ZeriFlow's free scanner automatically checks whether your session cookies have the SameSite flag set correctly. It flags cookies served without SameSite or with SameSite=None that lack the Secure attribute.
Run a free ZeriFlow scan on your domain to see your cookie security posture in seconds.
Double Submit Cookie Pattern
When server-side session storage isn't available (stateless APIs, microservices), the double submit cookie pattern provides CSRF protection without server state.
How it works:
- 1Generate a random CSRF token and set it as a cookie:
Set-Cookie: csrf=a9f3c2...; SameSite=Strict - 2The client reads this cookie (via JavaScript) and sends it in a request header:
X-CSRF-Token: a9f3c2... - 3The server validates that the cookie value matches the header value.
Because a cross-site attacker can't read your cookies (same-origin policy for JavaScript), they can't replicate the token in the header.
// Frontend: read cookie and attach to request
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCookie('csrf')
},
body: JSON.stringify({ amount: 100, to: 'bob' })
});Limitation: This pattern is weakened if your app has an XSS vulnerability — an XSS attacker can read the cookie and forge the header. Defense-in-depth matters.
CSRF Protection for SPAs and REST APIs
Modern single-page applications using JWT Bearer tokens in Authorization headers are inherently CSRF-safe — because browsers don't automatically attach Authorization headers to cross-site requests. The attack vector doesn't apply.
However, many SPAs still use cookie-based sessions for convenience. In that case:
- Use
SameSite=StrictorSameSite=Laxon session cookies. - Validate the
OriginorRefererheader server-side as an additional check. - Implement the double submit cookie pattern for stateless backends.
Origin Header Validation
def csrf_origin_check(request):
origin = request.headers.get('Origin')
allowed_origins = {'https://myapp.com', 'https://www.myapp.com'}
if origin and origin not in allowed_origins:
raise PermissionDenied('Invalid origin')This won't work alone (the header can be absent in some scenarios), but combined with SameSite cookies it adds a useful layer.
Common CSRF Mistakes to Avoid
1. Using GET requests for state-changing actions GET requests should be idempotent. Never use them for actions like delete, transfer, or update. Browsers prefetch links, search engines crawl URLs — GET-based state changes are a security disaster.
2. Relying on secret cookies alone
Even HttpOnly session cookies don't prevent CSRF — the attack doesn't need to read the cookie, just send it.
3. Validating the Referer header as the only defense Referer can be stripped by privacy tools, missing in some HTTPS-to-HTTP flows, and spoofed in some edge cases. Use it as a supplement, not a primary defense.
4. Skipping CSRF protection on AJAX endpoints
Attackers can use fetch() with mode: 'no-cors' to trigger state-changing requests to your API endpoints. Protect all state-changing endpoints, not just forms.
5. Not expiring CSRF tokens Tokens should expire with the session at minimum. Per-request tokens provide stronger protection but require careful UX handling.
FAQ
Q: Does HTTPS protect against CSRF attacks?
A: No. HTTPS encrypts data in transit and verifies server identity, but it doesn't prevent browsers from automatically attaching cookies to cross-site requests. A CSRF attack works perfectly on HTTPS sites. You need dedicated CSRF mitigations regardless of whether your site uses HTTPS.
Q: Do modern browsers block CSRF automatically?
A: Partially. Since 2020, most browsers default SameSite=Lax for cookies that don't specify a value, which blocks most CSRF attacks. However, you shouldn't rely on browser defaults — explicitly set SameSite=Strict on your session cookies and implement CSRF tokens for defense-in-depth.
Q: Is CSRF protection needed for REST APIs?
A: It depends on authentication method. If your API uses cookies for authentication, yes — CSRF protection is required. If it uses Bearer tokens in the Authorization header, CSRF is not a practical threat since browsers don't auto-attach those headers. Always check what authentication mechanism is in play.
Q: Can CSRF tokens be stored in localStorage?
A: Yes, and this is safe from CSRF (localStorage isn't auto-sent like cookies). However, localStorage is vulnerable to XSS — a script injection can steal any localStorage value. Prefer server-side session storage or the double submit cookie pattern when XSS risk exists.
Q: How does ZeriFlow detect CSRF vulnerabilities?
A: ZeriFlow scans your HTTP responses for missing or misconfigured SameSite cookie flags, absence of common CSRF token patterns, and other related misconfigurations. It's a passive scan — no login or credentials required. Try it free at ZeriFlow.
Conclusion
CSRF attack prevention requires a layered approach: CSRF tokens as your primary defense, SameSite=Strict cookies as a browser-enforced safety net, and origin validation as a backup. No single control is foolproof, but combining them makes CSRF exploitation practically impossible.
The first step is knowing where you stand. [Scan your site with ZeriFlow](https://zeriflow.com) — free, instant, no account required — and get a full report on your cookie security and CSRF posture in under a minute.
Don't wait for a breach to care about CSRF. One forged request can transfer funds, change passwords, or delete accounts. Fix it today.