CSRF Explained: SameSite Cookies and CSRF Tokens
Cross-site request forgery (CSRF) tricks a logged-in user's browser into sending a request to your site from another site. Because browsers attach cookies automatically, the request arrives with the victim's session.
The attack
<!-- on evil.example -->
<form action="https://bank.example/transfer" method="POST" id="f">
<input name="to" value="attacker"><input name="amount" value="50000">
</form>
<script>document.getElementById('f').submit()</script>
If bank.example relies only on a session cookie, the transfer goes through.
Defence 1: SameSite cookies
Set-Cookie: sid=abc123; HttpOnly; Secure; SameSite=Lax; Path=/
Lax- cookies are sent on top-level navigation (clicking a link) but not on cross-site POSTs, iframes or fetch requests. Modern browsers use Lax by default when no attribute is set.Strict- never sent cross-site, even when following a link (users arriving from an email link appear logged out).None- always sent; requiresSecure. Only for genuine cross-site use cases, and then you need other CSRF defences.
Defence 2: check Origin
const ALLOWED = new Set(['https://app.example.com']);
app.use((req, res, next) => {
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
const origin = req.get('Origin');
if (origin && !ALLOWED.has(origin)) return res.status(403).send('Bad origin');
next();
});
Browsers also send Sec-Fetch-Site; rejecting cross-site on state-changing requests is a simple, strong check.
Defence 3: CSRF tokens
A random token stored in the session and included in every form or request header. An attacker's site can't read it, so it can't forge a valid request. Most frameworks (Django, Rails, Laravel) include this.
Rules that always apply
- Never change state on GET. A GET can be triggered by an
<img>tag. - APIs authenticated with an
Authorization: Bearerheader (not cookies) aren't vulnerable to classic CSRF, because browsers don't attach that header automatically. - A permissive CORS policy with credentials (
Access-Control-Allow-Credentials: truefor any origin) undoes your protections.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.