How do I fix CSRF?

Direct answer

Set `SameSite=Lax` on your session cookie and use your framework's built-in CSRF token. If you authenticate with a bearer token in a header rather than a cookie, you aren't vulnerable to CSRF at all. The browser doesn't attach headers automatically, which is the entire mechanism.

Muhammad HasanUpdated

Cross-site request forgery works because browsers attach cookies to requests automatically, regardless of what triggered them. A form on an attacker's page can post to your site, and the user's session cookie goes along with it.

The user's browser makes the request, so it carries their identity. Your server can't tell the difference from the request alone.

First: do you actually have this problem

CSRF depends on the browser attaching credentials by itself. That's cookies.

If your API authenticates with Authorization: Bearer <token> and the token is held in memory or read from storage by your JavaScript, the browser attaches nothing automatically, and a cross-origin form post arrives unauthenticated. No CSRF.

This trips people up because a lot of CSRF advice predates token-based APIs. Work out how requests are authenticated before adding protection you may not need.

If you use cookies, including httpOnly cookies holding a JWT, which is a common and defensible choice. You need the protections below.

SameSite cookies

Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax

SameSite=Lax stops the cookie being sent on cross-site POST requests while still allowing normal top-level navigation. Most browsers now default to Lax, which has removed a large share of the classic attacks.

Strict is tighter and breaks inbound links from other sites, which is usually more disruption than it's worth. None reintroduces the problem and requires Secure.

Treat this as one layer, not the whole answer.

Synchroniser tokens

The standard approach, and what every major framework implements: the server issues a random token, embeds it in the form, and rejects requests without a matching value.

An attacker's page can make the request but can't read the token, because the same-origin policy stops them reading your page.

Rails, Django, Laravel and Spring all enable this by default. The failure mode is almost never that the framework didn't do it, it's that someone disabled it to fix an API endpoint that was failing. If a controller serves both browser and machine traffic, the machine side should authenticate differently rather than being exempted.

Double-submit cookies

Where server-side state is inconvenient: set the token in a cookie and require it in a header, then compare. Works because the attacker can't read the cookie to construct the header, though it's weaker if you have subdomains that can write cookies.

Where CSRF still bites

State-changing GET requests. A GET /account/delete is triggerable by an image tag. Use POST, PUT and DELETE for anything with an effect.

Login CSRF. An attacker logs the victim into an account the attacker controls, then reads what they do. Protect the login form too.

Exempted endpoints. Webhook receivers, file uploads and legacy integrations accumulate exemptions. Read the exemption list rather than assuming it's short.

Spotting it in an existing codebase

Scanners catch disabled CSRF middleware and exemption lists, because those are configuration with a recognisable shape. They can't tell you whether a specific endpoint changes state, which is what determines whether an exemption matters. That part needs a person.

Go deeper

Kolega for AppSec teams

Related answers

See what your own repository returns

Connect a repo and run a scan. No credit card, no pipeline changes.

Get started for free