CSRF
Cross-Site Request Forgery — an attack that tricks authenticated users into submitting unintended requests to a web application where they're already logged in.
What Is CSRF?
Cross-Site Request Forgery (CSRF, sometimes pronounced “sea-surf”) is a web security vulnerability that allows an attacker to trick an authenticated user into performing actions on a web application without their knowledge or consent. The attack exploits the browser’s automatic inclusion of credentials — cookies, HTTP authentication headers, client certificates — with every request to a given origin, regardless of which website initiated the request.
When a user is logged into their bank at bank.com, the browser attaches the session cookie to every request sent to bank.com. If the user visits a malicious page at evil.com, that page can trigger a request to bank.com/transfer?to=attacker&amount=10000. Because the browser automatically includes the bank.com session cookie, the bank’s server sees a fully authenticated request and processes the transfer. The user never intended to make that request, but the server has no way to distinguish it from a legitimate action.
CSRF was classified as a critical vulnerability in earlier editions of the OWASP Top 10 and remains a significant risk for applications that rely on cookie-based authentication without proper anti-CSRF defenses. While modern browsers and frameworks have introduced mitigations that reduce the attack surface, CSRF remains relevant — especially for applications that have not adopted the latest security standards or that support legacy browser configurations.
How It Works
A CSRF attack requires three conditions: the victim is authenticated to the target application, the target application uses cookies (or other automatic credentials) for session management, and there are no additional verification steps (tokens, re-authentication) for sensitive actions.
Here is a vulnerable Express.js endpoint that processes a password change:
// Vulnerable: no CSRF token verification
app.post('/account/change-password', (req, res) => {
const { newPassword } = req.body;
const userId = req.session.userId; // Session from cookie
db.updatePassword(userId, newPassword);
res.send('Password updated');
});
An attacker hosts a page that automatically submits a form to this endpoint:
<!-- Attacker's page at evil.com -->
<html>
<body onload="document.getElementById('csrf-form').submit()">
<form id="csrf-form" action="https://target.com/account/change-password" method="POST">
<input type="hidden" name="newPassword" value="hacked123" />
</form>
</body>
</html>
When an authenticated user visits this page, the form submits automatically, the browser attaches the target.com session cookie, and the password is changed to hacked123 without the user’s knowledge.
The primary defense is the Synchronizer Token Pattern — generating a unique, unpredictable token for each session (or each request) and requiring it with every state-changing request:
// Fixed: CSRF token validation with csurf middleware
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
// Render form with embedded CSRF token
app.get('/account/change-password', csrfProtection, (req, res) => {
res.send(`
<form action="/account/change-password" method="POST">
<input type="hidden" name="_csrf" value="${req.csrfToken()}" />
<input type="password" name="newPassword" placeholder="New password" />
<button type="submit">Change Password</button>
</form>
`);
});
// Validate CSRF token on submission
app.post('/account/change-password', csrfProtection, (req, res) => {
// csurf middleware automatically validates the _csrf token
const { newPassword } = req.body;
db.updatePassword(req.session.userId, newPassword);
res.send('Password updated');
});
Modern applications can also leverage the SameSite cookie attribute:
// Set session cookie with SameSite=Strict or SameSite=Lax
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict' // Cookie not sent on cross-origin requests
}
}));
With SameSite=Strict, the browser does not include the cookie with any cross-origin request, effectively eliminating CSRF for browsers that support the attribute. SameSite=Lax provides a balance by allowing cookies on top-level navigations (like following a link) but blocking them on cross-origin POST requests, form submissions, and AJAX calls.
Why It Matters
CSRF allows attackers to perform actions with the full authority of the victim’s authenticated session. Depending on the application, this could mean transferring funds, changing account credentials, modifying security settings, making purchases, deleting data, or granting access to an attacker-controlled account. The attack requires no vulnerability in the victim’s browser and no credential theft — it exploits the web’s fundamental trust model.
CSRF is particularly dangerous in applications where users have elevated privileges. If an administrator visits a malicious page, a CSRF attack could create new admin accounts, modify system configurations, or exfiltrate sensitive data — all without the attacker needing to compromise the administrator’s credentials.
The prevalence of single-page applications and API-based architectures has changed the CSRF landscape but not eliminated it. Applications that use token-based authentication (like JWTs in headers) instead of cookies are inherently resistant to CSRF because the authentication credential is not automatically attached by the browser. However, applications that store JWTs in cookies and read them server-side remain vulnerable.
Best Practices
- Use anti-CSRF tokens on all state-changing endpoints. Every POST, PUT, PATCH, and DELETE endpoint should require a valid CSRF token that the attacker cannot predict or obtain.
- Set the SameSite attribute on session cookies.
SameSite=Laxis the default in modern browsers, but explicitly setting it ensures consistent behavior across all supported browsers and prevents fallback toNone. - Verify the Origin and Referer headers. As a secondary defense, check that the
OriginorRefererheader on incoming requests matches your application’s domain. Reject requests with missing or mismatched origins for sensitive operations. - Require re-authentication for critical actions. Password changes, email changes, and financial transactions should require the user to re-enter their current password, providing a defense that is independent of CSRF tokens.
- Use custom request headers for API endpoints. AJAX requests can set custom headers (like
X-Requested-With) that cross-origin requests cannot set without CORS permission. Verifying the presence of a custom header blocks simple CSRF attacks.
Common Mistakes
- Assuming GET requests are safe. If your application performs state changes through GET requests (like
/delete-account?confirm=true), CSRF is trivial via an<img>tag. State changes must use POST, PUT, or DELETE methods, and these must validate CSRF tokens. - Relying solely on SameSite cookies. While
SameSite=Strictprovides strong CSRF protection, it does not protect against subdomain attacks or users on older browsers that do not support the attribute. Use SameSite as a defense-in-depth layer, not the sole protection. - Implementing CSRF tokens but not validating them. Including a CSRF token in the form but failing to check it on the server provides zero protection. This mistake is more common than it sounds, especially when middleware is misconfigured or when new endpoints are added without the CSRF middleware.
- Exempting API endpoints from CSRF protection. APIs that accept cookie-based authentication are vulnerable to CSRF just like form-based endpoints. If your API uses cookies for auth, it needs CSRF protection. If it uses bearer tokens in headers, it does not.
Related Terms
Learn More
Tool Reviews
Free Newsletter
Stay ahead with AI dev tools
Weekly insights on AI code review, static analysis, and developer productivity. No spam, unsubscribe anytime.
Join developers getting weekly AI tool insights.
Aikido Security
Checkmarx
Corgea
Fortify