CSRF stands for Cross-Site Request Forgery, an attack where a malicious page tricks a logged-in user's browser into sending an unwanted request to a site that trusts it. It works because browsers can attach cookies, including session cookies, to requests for that site that another site started. It is also written XSRF, and the standard defense is a per-session anti-CSRF token.
How it works
The target site sees a valid session cookie and cannot tell a real click from a forged request. The attacker never reads the response. They only need the request to cause a state change, such as a transfer, a password change or an email update. OWASP notes the attacker is limited to what the victim is allowed to do.
The main defenses, from the OWASP CSRF Prevention Cheat Sheet:
- Synchronizer token: the server generates a random token tied to the session and requires it in a header or form field on every state-changing request. Cookie validation alone does not count, because the browser sends cookies automatically.
- Signed double-submit cookie: a stateless variant where the token is an HMAC bound to the session. OWASP marks the naive variant, with no signature, as discouraged because an attacker who can write cookies on the domain can bypass it.
- Fetch metadata: check the Sec-Fetch-Site request header and block cross-site requests, with an Origin or Referer check as fallback for older browsers.
- SameSite cookies: the SameSite attribute (Strict, Lax or None) controls whether the browser sends the cookie on cross-site requests. OWASP treats it as defense in depth and accepts it alone only in narrow cases, such as no state change on GET and no shared registrable domain.
- Safe methods stay safe: never change state on GET.
const token = sid => crypto.createHmac('sha256', key).update(sid).digest('base64url');
// on every POST: compare req.headers['x-csrf-token'] with token(sid) using timingSafeEqual
Running a small Node.js server with that check and three POST requests gave:
token length: 43
no token 403 CSRF token missing or wrong
wrong token 403 CSRF token missing or wrong
correct token 200 transferred
Does SameSite=Lax stop CSRF by default?
Mostly, but not completely. Under the RFC 6265bis draft, a cookie with no SameSite attribute or an unrecognized value gets a default that is equivalent to Lax, and Lax cookies are sent cross-site only on top-level navigations with safe methods such as GET. The draft also lets browsers apply "Lax-allowing-unsafe" to cookies with no SameSite attribute, which sends a recently created cookie (the draft suggests 2 minutes or less) on cross-site top-level POST requests. In a test with headless Chromium 141, a cross-site form POST right after login carried a cookie with no SameSite attribute, and carried nothing when the cookie was marked Lax or Strict. After 130 seconds, the same POST carried no cookie at all. Always set SameSite explicitly.
Common pitfalls
- State changes on GET: SameSite=Lax still sends the cookie on cross-site GET navigations, so a delete link or a "?action=transfer" endpoint remains forgeable.
- Relying on the default SameSite: a cookie with no attribute can still be sent on a cross-site POST shortly after it is issued. Set Lax or Strict explicitly.
- Checking only that a token cookie exists: the browser sends the cookie for the attacker too. Compare a value from the header or form body to the server-side record.
- Same site is not same origin: a request from any subdomain of the same registrable domain counts as same-site, so a vulnerable sibling subdomain bypasses SameSite. Use tokens.
- Tokens in URLs: query strings leak into logs, browser history and Referer headers. Send tokens in a header or POST body.
- Skipping the login form: login CSRF can sign a victim into the attacker's account. Use a pre-session token on the login form too.
Related terms
- XSS — script injection that OWASP says can defeat every CSRF mitigation
- CORS — controls which origins may read responses, yet a simple cross-site form POST reached the server in the test above
- HTTP — the request and cookie mechanics that make the attack possible
- HMAC — builds the signed double-submit token
- JWT — a token format that is sent automatically, like any cookie, if you store it in one
See also