TL;DR
CSRF tricks logged-in users into unknowingly performing actions on your site. When a user visits a malicious page, it can submit forms or make requests to your site using the user's existing session cookies. Protect against CSRF with SameSite cookies, CSRF tokens, and by checking the Origin header on sensitive requests.
What Is CSRF?
Cross-Site Request Forgery (CSRF, pronounced "sea-surf") is an attack that forces authenticated users to perform unwanted actions. Unlike XSS where attackers inject code into your site, CSRF uses the victim's own browser to make legitimate-looking requests.
Here's a simple scenario:
- You're logged into your banking website
- You visit a malicious site (or one that's been hacked)
- That site contains code that submits a money transfer request to your bank
- Your browser automatically includes your banking cookies
- The bank thinks you made the request and transfers the money
How CSRF Attacks Work
Basic Form Attack
<!-- On evil-site.com -->
<html>
<body onload="document.forms[0].submit()">
<form action="https://yourbank.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker-account" />
<input type="hidden" name="amount" value="10000" />
</form>
</body>
</html>
<!-- When a logged-in user visits this page,
their browser submits the form with their cookies -->
Image-Based Attack
<!-- GET requests can be triggered by images -->
<img src="https://yoursite.com/api/delete-account" style="display:none" />
<!-- Or change settings -->
<img src="https://yoursite.com/api/settings?email=attacker@evil.com" />
Why it works: Browsers automatically include cookies when making requests to a site, regardless of where the request originates. The server can't tell if the request came from your legitimate page or a malicious one.
Real-World CSRF Consequences
| Attack Target | Potential Damage |
|---|---|
| Email settings | Change recovery email, take over account |
| Password change | Lock user out of their account |
| Payment actions | Transfer money, make purchases |
| Admin functions | Add attacker as admin, modify data |
| Social actions | Post content, follow accounts, share data |
How to Prevent CSRF
1. Use SameSite Cookies
SameSite cookies won't be sent on cross-site requests:
// Express.js session with SameSite
app.use(session({
cookie: {
httpOnly: true,
secure: true, // Requires HTTPS
sameSite: 'strict' // Or 'lax' for less strict protection
}
}));
// Next.js API route
res.setHeader('Set-Cookie',
'session=abc123; HttpOnly; Secure; SameSite=Strict');
| SameSite Value | Behavior | Use Case |
|---|---|---|
| Strict | Never sent cross-site | Maximum security |
| Lax | Sent on top-level navigations only | Good balance (default in modern browsers) |
| None | Always sent (requires Secure) | Third-party integrations |
2. Use CSRF Tokens
Include a unique token in forms that must match the server's expected value:
// Generate token and store in session
const csrfToken = crypto.randomUUID();
req.session.csrfToken = csrfToken;
// Include in form
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="{csrfToken}" />
<!-- other fields -->
</form>
// Verify on submission
if (req.body._csrf !== req.session.csrfToken) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
3. Check Origin Header
function validateOrigin(req, res, next) {
const origin = req.headers.origin || req.headers.referer;
const allowedOrigins = ['https://yoursite.com'];
if (origin && !allowedOrigins.some(o => origin.startsWith(o))) {
return res.status(403).json({ error: 'Invalid origin' });
}
next();
}
// Apply to state-changing routes
app.post('/api/*', validateOrigin);
4. Require Re-authentication for Sensitive Actions
// For sensitive actions, require password confirmation
app.post('/api/change-email', async (req, res) => {
const { newEmail, currentPassword } = req.body;
// Verify current password before making changes
const isValid = await verifyPassword(req.user.id, currentPassword);
if (!isValid) {
return res.status(401).json({ error: 'Invalid password' });
}
// Now safe to change email
await updateUserEmail(req.user.id, newEmail);
});
CSRF Protection in Frameworks
| Framework | Built-in Protection | How to Enable |
|---|---|---|
| Next.js | None by default | Use next-csrf or similar package |
| Express | None by default | Use csurf middleware |
| Django | Enabled by default | Include {% csrf_token %} in forms |
| Rails | Enabled by default | protect_from_forgery included |
Modern browsers help: Modern browsers default to SameSite=Lax for cookies without explicit SameSite setting, which prevents most CSRF attacks on POST requests.
What is CSRF?
CSRF (Cross-Site Request Forgery) is an attack that tricks a logged-in user into unknowingly submitting a request to a website. Because the user's browser automatically includes cookies, the site thinks the request is legitimate.
Do SameSite cookies prevent CSRF?
SameSite cookies provide good protection against CSRF. Setting SameSite=Strict prevents cookies from being sent on cross-site requests. SameSite=Lax allows cookies on top-level navigations but blocks them on cross-site POST requests.
Does using JSON APIs prevent CSRF?
JSON APIs have some natural CSRF protection because browsers won't send JSON in cross-origin requests without CORS approval. However, if your API accepts form-encoded data or has permissive CORS settings, it may still be vulnerable.
What's the difference between CSRF and XSS?
XSS injects malicious code into your website that runs in users' browsers. CSRF tricks users' browsers into making requests to your site. XSS exploits trust the user has in your site; CSRF exploits trust your site has in the user's browser.
Check Your CSRF Protection
Our scanner tests your forms and APIs for CSRF vulnerabilities.
Start Free Scan