SeguridadCSRFAutenticaciónSameSiteRevisión de código

SameSite=Lax doesn't protect you from login-CSRF

Published on 2026-08-31 · Xiliux

We built an identity server — Bachué — with password login, a second factor, and cookie sessions. The cookies carry SameSite=Lax, and we took for granted what "everyone knows": that SameSite=Lax protects against CSRF.

We didn't trust our own review. The author of a piece of code is the worst placed to audit it: what they reasoned already feels verified. So we asked three different models to attack it, each on its own, with a single brief: find the most serious defect; think like someone trying to break in.

A false alarm, then the real one

The first model shouted "CSRF!" with the wrong mechanics: it believed SameSite=Lax lets a cross-site POST through. It doesn't — Lax won't send the cookie on a POST from another site. A second model refuted it. So far, noise.

The third found the real one, and it's subtle: login-CSRF, which SameSite does not cover.

Why Lax protects logout but not login

SameSite=Lax protects endpoints that need the victim's cookie: logging out, changing settings. A POST from the attacker's site arrives without that cookie, the server sees no session, and nothing happens.

But /login and /register are different: they don't need the victim's cookie. The attacker puts their own credentials in the form, and all the server does is set a new cookie in the response. And a first-party Set-Cookie is honored no matter where the request came from. The result: the victim ends up browsing authenticated as the attacker, without realizing it — and everything they type afterward lives in the attacker's account. SameSite has no effect on this.

Claiming isn't proving

"SameSite covers CSRF" was a claim, and like every security boundary, it seemed to hold until someone crossed it. The proof — three independent adversaries — took minutes and found what a single look, ours included, would have passed.

The fix is one line of discipline: every state-changing POST — including the ones that don't yet require a session — checks that the request is first-party (Sec-Fetch-Site) before processing it. As a rule, not a per-endpoint patch the next endpoint forgets.

The lesson is worth more than the bug: reviewing your own security code isn't enough. Put independent adversaries to prove the boundary holds. If you only claim it, one day it won't — silently, at the spot no one looked at again.

FAQ

Doesn't SameSite=Lax protect against CSRF?

It protects endpoints that NEED the victim's cookie (logout, settings): a cross-site POST arrives without the cookie and fails. But it doesn't protect against login-CSRF, because /login doesn't need the victim's cookie — the attacker uses their own credentials and the server sets a new cookie.

How do you defend against login-CSRF?

With a first-party check on EVERY state-changing POST, including the ones that don't require a session: verify Sec-Fetch-Site (or a CSRF token) before processing. As a global rule, not endpoint by endpoint.

Why review with several models?

The author is the worst placed to audit their own code. Independent models, different from one another, recalibrate one's false alarms and find what another misses; here one got the mechanics wrong, another refuted it, and a third found the real bug.

← More articlesRequest a quote