CSRF Attacks Explained: Prevent Cross-Site Request Forgery
CSRF tricks authenticated users into performing unintended actions. Learn how CSRF attacks work and modern defenses with tokens and SameSite cookies.

Cross-Site Request Forgery (CSRF) is an attack that forces authenticated users to perform actions they didn't intend. If a user is logged into their bank and visits a malicious page, that page can submit a hidden form that transfers money — using the user's existing session. CSRF exploits the trust a website has in the user's browser.
How CSRF Attacks Work
The Attack Scenario
- The victim is authenticated on
bank.com(has a valid session cookie) - The victim visits
evil.comwhile still logged in evil.comcontains a hidden form that posts tobank.com/transfer- The browser automatically includes the
bank.comsession cookie with the request - The bank processes the transfer because the request appears legitimate
CSRF Attack Example
<!-- On evil.com - auto-submitting form -->
<form action="https://bank.com/transfer" method="POST" id="csrf-form">
<input type="hidden" name="to" value="attacker-account">
<input type="hidden" name="amount" value="10000">
</form>
<script>document.getElementById('csrf-form').submit();</script>
GET-Based CSRF
If state-changing operations use GET requests, CSRF is even simpler:
<!-- A simple image tag triggers the attack -->
<img src="https://bank.com/transfer?to=attacker&amount=10000">
Modern CSRF Defense Strategies
1. SameSite Cookie Attribute (Primary Defense)
The SameSite cookie attribute is the most effective modern CSRF defense:
Set-Cookie: session=abc123; SameSite=Lax; Secure; HttpOnly
- SameSite=Strict: Cookie is never sent on cross-site requests. Maximum security but breaks legitimate cross-site navigation (e.g., clicking a link to your site from email).
- SameSite=Lax: Cookie is sent on top-level navigations (clicking links) but blocked on cross-site POST requests, form submissions, and AJAX calls. Best balance of security and usability.
- SameSite=None: Cookie is always sent (requires Secure flag). No CSRF protection — only use when cross-site access is intentional.
2. CSRF Tokens (Synchronizer Token Pattern)
Generate a unique, unpredictable token for each user session and require it on all state-changing requests:
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="UNIQUE_RANDOM_TOKEN">
<!-- other form fields -->
</form>
The server validates the token on every POST request. Since the attacker's page cannot read the token (blocked by Same-Origin Policy), they cannot forge a valid request.
3. Double Submit Cookie Pattern
Send the CSRF token as both a cookie and a request parameter. The server verifies they match:
// Set CSRF cookie
Set-Cookie: csrf=TOKEN; SameSite=Strict; Secure
// Include in request header
X-CSRF-Token: TOKEN
4. Custom Request Headers
Require a custom header on state-changing requests. Browsers don't allow cross-origin requests with custom headers without CORS preflight:
// Client-side
fetch('/api/transfer', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
body: data
});
// Server-side: reject requests without the header
Framework-Specific Implementation
Express.js with csurf
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/submit', csrfProtection, (req, res) => {
// Token is automatically validated
res.send('Success');
});
Django
Django includes CSRF protection by default. Include the template tag in forms:
<form method="POST">
{% csrf_token %}
<!-- form fields -->
</form>
Next.js API Routes
For Next.js applications, combine SameSite cookies with custom headers:
// Verify the Origin header matches your domain
export async function POST(request) {
const origin = request.headers.get('origin');
if (origin !== 'https://yourdomain.com') {
return new Response('Forbidden', { status: 403 });
}
// Process the request
}
Common CSRF Mistakes
- Using GET for state changes: GET requests should never modify data. Use POST, PUT, or DELETE.
- CSRF token in URL: Tokens in query strings leak via Referer headers and browser history.
- Per-session tokens only: Use per-request or per-form tokens for stronger protection.
- Not validating on all endpoints: Every state-changing endpoint needs CSRF protection.
- Relying on SameSite=None: This provides zero CSRF protection.
CSRF Prevention Checklist
- Set SameSite=Lax on all session cookies (minimum defense)
- Implement CSRF tokens for all state-changing forms
- Never use GET requests for operations that modify data
- Validate Origin and Referer headers on the server
- Use framework-provided CSRF protection
- Test CSRF defenses with different browsers
- Run a SecScanner scan to verify Anti-CSRF token implementation
CSRF attacks exploit the most fundamental trust model of the web — that browsers automatically send credentials with every request. Modern defenses like SameSite cookies have dramatically reduced CSRF risk, but defense in depth with tokens and header validation remains best practice for high-security applications.
Related Articles
Check Your Website Security
Want to see how your website measures up? Run a free security scan with SecScanner to identify vulnerabilities and get actionable remediation guidance.
Scan Your Website Free