A forged slip riding on a real badge
Watch it happen
A forged transfer slip travels to the teller on Alice’s valid badge. The teller trusts the badge and moves the money.
Alice is logged in. Her badge cookie has no SameSite protection (None, or a browser with no Lax default), so her browser attaches it to anything sent to the bank, even from other sites.
In bank terms and app terms
| In the bank | In your app |
|---|---|
Origin header; SameSite cookies make the browser hold the badge back |
Are you exposed?
You use cookie-based login and your POST, PUT or DELETE routes only check that the user is logged in. Your session cookie uses SameSite=None, or sets noSameSiteand relies on a browser default (only some browsers default toLax).Your forms have no hidden token, and your server never looks at the Originheader.
Set session cookies with SameSite=Lax(orStrict),HttpOnlyandSecure.On every route that changes data, require a CSRF token, or check that Originmatches your own site.Never change data on a GET request. Keep GETs read-only.
It has happened to real companies
Netflix (2008) — At the Black Hat DC conference, security researcher Chuck Willis of Mandiant demonstrated a live cross-site request forgery attack on Netflix that added a movie to a logged-in user's queue without their knowledge. He said earlier CSRF problems at Netflix could have changed a user's mailing address, but had been partly fixed. A site that accepts state-changing requests without a CSRF token lets any page the victim visits act as them.
ING Direct (2008) — CSRF allowed attackers to initiate fund transfers from a victim's bank account when they visited a malicious page while logged in.
What it looks like in code
// GOOD: Use SameSite cookies (modern defense)
response.cookies.set("session", token, {
httpOnly: true,
secure: true,
sameSite: "strict" // or "lax" for GET-safe scenarios
})
// GOOD: Validate Origin header on mutations
const origin = request.headers.get("Origin")
if (origin !== "https://your-app.com") {
return new Response("Forbidden", { status: 403 })
}
Ask your AI tool to fix it
Paste this into Cursor, Claude, Lovable, Bolt or whatever you build with.
Protect this app against CSRF. Set all auth cookies with SameSite=Lax (Strict where possible), HttpOnly and Secure. Add middleware that rejects POST, PUT, PATCH and DELETE requests when the Origin header is missing or does not match our own origin, and add CSRF tokens to forms that need them. Make sure no GET route changes data. List each route you protected.Where this comes from
The facts, figures and fixes on this page were checked against these sources on 25 Oct 2026. They are linked so you can check for yourself.
- Cross-Site Request Forgery Prevention Cheat SheetOWASPCSRF tokens, Origin checks, SameSite as extra layer, no state change on GET
- Cross-site request forgery (CSRF)MDN Web DocsHow the browser attaches cookies to forged requests; tokens and SameSite fixes
- Set-Cookie header (SameSite)MDN Web DocsSameSite Lax/Strict/None behaviour; only some browsers default to Lax; Secure, HttpOnly
- Black Hat: Netflix CSRF VulnerabilityInternetNewsWillis's 2008 live Netflix demo adding a movie; address-change claim, partly fixed
- Netflix CSRF RevisitedAppSec NotesNetflix fixed address changes but left the add-movie action open
- Popular Websites Vulnerable to Cross-Site Request Forgery AttacksPrinceton CITP2008 ING Direct CSRF allowed moving money out of accounts; later fixed
Spot a mistake or something out of date? Report a correction · How these guides are made and checked
The free, open-source scanner checks for most of the problems in these guides.