Skip to content

// Security · 08

Cross-Site Request Forgery

Victim browserleads toauto cookieAuto-sent cookieleads toUnwanted change

Another site makes your browser submit a request as you.

How it fits together

Cross-Site Request Forgery structure«client»Logged-in browsersession cookie storedAttacker pageauto-submitting formBank siteSameSite=Lax cookietoken checked firstRejected request1 visits page2 forged POST3 no valid token
uses / callsfails / error path· numbered steps replay in order
  • Logged-in browser uses / calls Attacker page (1 visits page)
  • Attacker page fails / error path Bank site (2 forged POST)
  • Bank site fails / error path Rejected request (3 no valid token)

Spot it

  • Transfer triggered by a POST from another site
  • Cookie-based login on forms without tokens
  • Session cookie has no SameSite attribute

It costs

A token per form to issue and check; SameSite can break some legitimate cross-site flows.

In code

using Microsoft.AspNetCore.Antiforgery;​builder.Services.AddAntiforgery(options =>{    options.Cookie.Name = "orders-csrf";    options.Cookie.SameSite = SameSiteMode.Strict;    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;});​app.MapPost("/transfers", async (    HttpContext http,    IAntiforgery antiforgery,    TransferRequest request) =>{    await antiforgery.ValidateRequestAsync(http); // throws when the token is missing or wrong    return Results.Accepted();});
Go deeperThe full explanation and what to ask for · 1 min readExpand

CSRF needs three things: the victim is logged in, the browser attaches the session cookie automatically to requests for your site, and the attacker can make the browser send a request that changes state, such as a form POST from another page. The attacker cannot read the response, so the goal is the side effect: a transfer, a password change.

Two defences do most of the work. SameSite cookies stop the browser attaching the cookie to cross-site requests: Strict never sends it cross-site, and Lax sends it only on top-level GET navigations, so a cross-site POST arrives without a session. Anti-forgery tokens are secrets the attacker's page cannot read: the server issues one tied to the session, the form carries it, and unsafe requests without it are refused. Checking Origin on unsafe requests adds a further layer.

APIs that authenticate with a bearer token in the Authorization header are not exposed the same way, because the browser does not attach that header by itself. The risk returns if the token is kept in a cookie. Keep GET endpoints free of state changes, which is the simplest defence of all. SameSite alone is not enough: subdomains share a site, so a takeover of one can send same-site requests.

Ask for it

  • cookie-based session
  • SameSite (Strict, Lax, None)
  • anti-forgery (synchronizer) token
  • double-submit cookie
  • Origin header check
  • state-changing request

Protect cookie-authenticated forms: session cookie SameSite=Lax, Secure, HttpOnly; an anti-forgery token bound to the session, checked before any POST, PUT or DELETE does work; reject unsafe requests whose Origin is present and foreign. Test that a POST without a token changes nothing.