CSRF in 2026 — still a thing

SameSite=Lax made classic CSRF hard. Rare doesn't mean gone — and several modern patterns bring it back.

· LoginWith team

When browsers made SameSite=Lax the default for cookies around 2020-2021, a lot of teams threw out their CSRF tokens. The attack that classic CSRF protection defended against was suddenly impossible by default. Except… it’s not.

What SameSite=Lax actually does

A Lax cookie is sent with:

  • Top-level GET navigations (clicking a link)
  • Fetch requests from the same registrable domain

It’s not sent with:

  • Cross-site POST from a form
  • Cross-site fetch()/XHR
  • Cross-site iframes

So the classic CSRF attack — a hidden <form action="https://victim.com/transfer" method="POST"> on attacker.com that auto-submits — no longer works. The cookie doesn’t ride along, the request is unauthenticated, the transfer fails.

Great. But:

Where CSRF comes back

1. Legacy endpoints that also accept GET

Some older APIs accept both GET and POST for the same action. GET /delete-account?id=42 gets hit with SameSite=Lax’s top-level navigation rule — the cookie IS sent. A link on attacker.com pointing to that URL, clicked by the victim, executes the action.

Audit your app: no state-changing operation should respond to GET. Ever.

2. Subdomain user content

If users can host content on *.example.com (blogs, landing pages, user profiles with HTML), those subdomains are “same site” as your main app. SameSite=Lax allows them to POST to your main site, and the cookie rides along. Suddenly CSRF is back, on a subdomain you thought was safe.

Defense: use separate domains for user content (usercontent.example.net), or add a CSRF token for state-changing requests.

3. Embeddable widgets

If your app is embedded as an iframe anywhere (Stripe checkout, a payment widget, a chat support tool), and you’ve set SameSite=None; Secure to make that work, you’re back to classic CSRF territory in those contexts.

Defense: for embedded flows, use a separate token bound to the request.

4. Sec-Fetch-Site check as a cheap fallback

Most modern browsers send a Sec-Fetch-Site header on every request:

  • same-origin — from the same origin
  • same-site — from the same registrable domain
  • cross-site — from elsewhere
  • none — no referrer (direct navigation)

For state-changing endpoints, you can reject anything that’s not same-origin. It’s a one-header check and catches most real-world CSRF attempts.

The rule

Modern CSRF defense isn’t “SameSite=Lax and forget.” It’s:

  1. No state change on GET
  2. Audit your subdomain boundaries
  3. Add a CSRF token OR Sec-Fetch-Site check on mutating endpoints

Do all three and you’ve closed the gaps that SameSite=Lax alone doesn’t.

Want auth that just works?

Get started with LoginWith