Join our Newsletter — 33% off our NHI Course
Home› Guides› CSRF Prevention Guide: Tokens, SameSite Cookies and Framework…
Guide Web & API Security

CSRF Prevention Guide: Tokens, SameSite Cookies and Framework Defences

PDF for newsletter subscribers · free to read online

← All guides
By Lalit Choda, NHI Mgmt Group Updated 24 October 2026 13 min read
On this page

Cross-site request forgery (CSRF) lets another website make a signed-in user's browser send a state-changing request that the server cannot tell apart from a real one. The weakness exists wherever an application authenticates requests only with credentials the browser attaches automatically, such as session cookies or HTTP authentication. This guide explains how the attack works, where it still appears despite SameSite defaults, and how to layer framework tokens, Fetch Metadata checks, origin verification and cookie settings so that one gap does not become an incident. It is written for developers, application security engineers and testers. For the wider family of web attacks see the Web Application Attacks Guide, and for cookie and header values see the Security Headers Guide.

Key takeaways

  • Use the framework's built-in protection first. Django, Spring Security, ASP.NET Core, Rails and Go 1.25 all ship CSRF defences; most real failures come from exemptions and disabled middleware, not from missing code.
  • Fetch Metadata is now the primary signal. OWASP recommends rejecting unsafe methods when Sec-Fetch-Site is cross-site, with origin checks or tokens as a mandatory fallback for clients that omit the header.
  • SameSite is defence in depth, not the fix. Lax still sends cookies on top-level GET navigations, and sibling subdomains count as same-site.
  • Never change state with GET. Every framework treats GET, HEAD and OPTIONS as safe and skips CSRF checks on them.
  • XSS defeats every CSRF control. Script running in your origin can read tokens and send same-origin requests, so CSRF work depends on XSS prevention.

How the attack works

MITRE CWE-352 describes the weakness as an application that does not, or cannot, sufficiently verify whether a request was intentionally sent by the user who issued it. Alternate names are session riding and XSRF. The attack pattern is catalogued as CAPEC-62.

  1. Ambient credentials: the victim is signed in to the target application, which tracks the session with a cookie.
  2. Attacker-controlled page: the victim visits a page or opens an email that the attacker controls.
  3. Forged request: that page causes the browser to send a request to the target, for example a form submission. The browser attaches the target's cookies according to their SameSite setting.
  4. Server accepts: if the server checks only the cookie, it performs the action with the victim's privileges. The attacker never sees the response; the damage is the state change itself.

The attacker needs only to predict the request: URL, parameters and values. OWASP WSTG-SESS-05 notes that switching from GET to POST does not prevent CSRF, because forms and scripts can submit POST requests across sites.

Variants worth naming

VariantWhat happensPrimary control
Classic CSRFCross-site form or navigation triggers a state change with the session cookieFramework token or Fetch Metadata check
Login CSRFThe victim is signed in to the attacker's account, so later activity (searches, saved cards, uploads) lands in an account the attacker can readToken on the login form via a pre-session, new session ID after login
Client-side CSRFYour own JavaScript builds a request from attacker-controlled input such as the URL fragment or postMessage data; the request is same-origin, so tokens and SameSite do not helpDo not build request targets from untrusted input; allow-list endpoints and methods
Subdomain-assisted CSRFA compromised or taken-over sibling subdomain is same-site and can plant cookies on the parent domainSigned tokens, __Host- cookies, treat same-site as untrusted
OAuth callback CSRFA forged authorisation response links the victim to the attacker's identity or tokenPKCE or state validation (ASVS V10.2.1)

Where it shows up

  • Exempted endpoints: views marked csrf_exempt in Django, ignoringRequestMatchers in Spring, [IgnoreAntiforgeryToken] or .DisableAntiforgery() in ASP.NET Core, often added to fix a broken integration and never revisited.
  • Disabled protection on "APIs" that use cookies: Spring's documentation says to disable CSRF only for stateless APIs whose non-browser clients do not use cookie authentication. A single-page app talking to its own API with a session cookie is not that case.
  • State-changing GET handlers: logout, unsubscribe, "approve" links and legacy admin actions. Go's CrossOriginProtection always allows GET, HEAD and OPTIONS, as do the other frameworks' defaults.
  • APIs accepting simple content types: an endpoint that accepts application/x-www-form-urlencoded, multipart/form-data or text/plain can be reached by a plain cross-site form without a CORS preflight. ASVS V3.5.2 requires that preflight-dependent functionality cannot be called without one.
  • Internal applications: WSTG notes that intranet tools are reachable because the victim's browser can reach them even when the attacker cannot. Routers, printers and admin consoles with default cookie settings are common examples.
  • Self-vulnerable applications: webmail, forums and ticketing systems where content viewed inside the application can itself trigger requests.

Impact

CWE-352 lists gaining privileges or assuming identity, bypassing protection mechanisms, reading or modifying data and denial of service. The attacker's reach is limited only by the victim's privileges, so a forged request against an administrator can mean full application compromise.

Target actionTypical consequence
Change email address or recovery phoneAccount takeover through the password reset flow. See the Account Takeover Prevention Guide.
Create a user, add a role or register an API keyPersistent access that outlives the victim's session
Add an OAuth client, webhook or SSO connectionA new machine identity under attacker control
Payments, transfers, ordersDirect financial loss
Security settings (disable MFA, open IP allow-list)Weakened defences ahead of a second-stage attack

The identity angle is real here: admin consoles that mint API keys, service accounts or integration tokens turn one forged request into a long-lived non-human credential. Protect those endpoints with re-authentication as well as CSRF tokens, and review them as part of the Token and Session Security Guide controls.

Detection

CSRF requests look legitimate at the application layer, so detection relies on the signals the defences produce and on testing.

  • Validation failures: log every rejected token or Fetch Metadata decision with endpoint, method, Sec-Fetch-Site, Origin and user. Django returns 403 through CSRF_FAILURE_VIEW; Spring raises InvalidCsrfTokenException; ASP.NET Core minimal APIs return 400 on failed form validation. A spike on one endpoint for many users suggests a live campaign; a spike after a release suggests a bug.
  • Report-only Fetch Metadata: web.dev and OWASP both recommend running a resource isolation policy in logging mode first. The same logs become a detection feed once enforced.
  • Sensitive change telemetry: alert on email, MFA, role and API key changes where Origin is absent, null or foreign.
  • Testing: WSTG-SESS-05 asks testers to confirm whether sessions rely only on automatically sent credentials and to retest in a browser that does not default to Lax, because a failed attempt in one browser proves nothing. Spring's test support (csrf().useInvalidToken()) lets teams assert a 403 in unit tests. See the Web Application Security Testing Guide.
  • Code review: grep for exemption markers (csrf_exempt, csrf.disable(), ignoringRequestMatchers, IgnoreAntiforgeryToken, DisableAntiforgery, skip_forgery_protection, AddInsecureBypassPattern) and require a justification for each.

Metrics

  • Number of CSRF exemptions per application, with an owner and review date for each (target: falling).
  • Percentage of state-changing routes covered by framework validation, measured from route inventories.
  • Session cookies with explicit SameSite and a __Host- prefix, as a share of all session cookies.
  • State-changing GET handlers found in review or DAST (target: zero).
  • Rejected requests per 10,000 unsafe requests after enforcement, split by legitimate breakage and attack.

Prevention and hardening

Choosing the control

ControlHow it worksBest fitLimits
Synchroniser tokenServer-generated secret tied to the session, sent in a hidden field or custom header, checked on every unsafe requestServer-rendered, stateful appsPer-request tokens can break the Back button; never put tokens in URLs or a cookie for this pattern
Signed double-submit cookieHMAC-SHA-256 over a session-dependent value and a random value, sent as cookie and as header or field, compared in constant timeStateless apps and horizontally scaled servicesMust be bound to session data that changes at login; the naive unsigned variant is insecure
Fetch Metadata (Sec-Fetch-Site)Reject unsafe methods when the browser reports cross-siteAny HTTPS app; low effort, central enforcementNeeds a fallback for clients that omit the header; HTTPS only
Origin and Referer verificationCompare the full origin with the expected value from configurationFallback for Fetch MetadataMissing or null must fail closed for sensitive actions; Referrer-Policy: no-referrer can break it
Custom request headerRequire a non-safelisted header so cross-origin calls need a CORS preflightJSON APIs called by your own front endBreaks if CORS allows credentials from broad or regex origins
SameSite Lax or StrictBrowser withholds the cookie on cross-site subrequestsEvery session cookie, as a second layerSame-site siblings, top-level GET, client-side CSRF, browser defaults vary
Re-authentication or one-time codeUser proves presence for a high-impact actionPassword, email, MFA, payments, key creationFriction; OWASP says CAPTCHA is not a CSRF defence

Framework settings to know

FrameworkDefault behaviourSettings and markers
DjangoCsrfViewMiddleware rejects unsafe methods without a valid token; masks the token per response against BREACH; checks Origin, then strict Referer on HTTPScsrfmiddlewaretoken, X-CSRFToken header, CSRF_TRUSTED_ORIGINS, CSRF_USE_SESSIONS, csrf_exempt
Spring SecurityEnabled by default for unsafe methods; token in HttpSessionCsrfTokenRepository; XorCsrfTokenRequestAttributeHandler adds BREACH protection_csrf parameter, X-CSRF-TOKEN / X-XSRF-TOKEN, CookieCsrfTokenRepository, csrf.spa(), ignoringRequestMatchers
ASP.NET CoreSynchroniser tokens via Data Protection; form tag helper adds __RequestVerificationToken; minimal API middleware validates POST, PUT and PATCH[AutoValidateAntiforgeryToken], [ValidateAntiForgeryToken], UseAntiforgery() after authentication and authorisation, HeaderName
Railsprotect_from_forgery enabled for new apps; token in forms and meta tagswith: :exception, csrf_meta_tags, authenticity_token, X-CSRF-Token
Go 1.25+http.CrossOriginProtection checks Sec-Fetch-Site, then Origin against Host; returns 403AddTrustedOrigin, AddInsecureBypassPattern, SetDenyHandler

Two details catch teams out. In ASP.NET Core, AddControllers does not enable antiforgery; use AddControllersWithViews or add the filter. In minimal APIs, DELETE is not validated by the middleware, so call IAntiforgery.ValidateRequestAsync explicitly. Go's CrossOriginProtection allows requests that carry neither Sec-Fetch-Site nor Origin, treating them as same-origin or non-browser, so pair it with authentication that is not cookie-based for those clients.

When to choose what

  • Server-rendered monolith: keep the framework token on, add Fetch Metadata at the edge or middleware, set session cookies SameSite=Lax (or Strict for admin sessions).
  • Single-page app with a cookie session: use the framework's SPA mode (for example Spring csrf.spa()) or a signed double-submit token sent as a header, require JSON, and reject simple content types.
  • API with bearer tokens in the Authorization header: Microsoft notes the browser does not attach these automatically, so CSRF does not apply. The trade-off is that tokens in local storage are readable by injected script; see the Session Hijacking and Cookie Theft Guide.
  • Small team, many legacy apps: a central Fetch Metadata policy at a reverse proxy gives broad coverage quickly. Large organisations should also standardise a hardened framework baseline and fail builds that add unreviewed exemptions.

Implementation sequence

  1. Inventory: list every state-changing route, its method, authentication type and any exemption. Remove state changes from GET handlers first.
  2. Restore framework defaults: re-enable middleware, convert exemptions to narrowly scoped ones with an owner.
  3. Cookies: set session and CSRF cookies to __Host-, Secure, explicit SameSite, no Domain. Do not rely on browser Lax-by-default; MDN says only some browsers apply it, with a two-minute POST allowance.
  4. Fetch Metadata in log-only mode: allow same-origin and none, decide on same-site, allow top-level GET navigations, log the rest. Add Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site where responses differ.
  5. Pilot: enforce on one application and watch cross-site flows such as payment returns, SSO callbacks and partner form posts; exempt only those, by exact origin.
  6. Enforce and fall back: reject with 403; for requests without Fetch Metadata, verify Origin or the token, and fail closed on sensitive endpoints.
  7. Step-up: add re-authentication for password, email, MFA and key creation.
  8. Rollback: keep the policy behind a configuration flag so enforcement can return to log-only in minutes, without removing tokens.

Common failure patterns

  • Exemption creep: an endpoint is exempted to fix a partner integration or webhook and later gains a cookie-authenticated browser use. Scope exemptions to exact routes and review them each release.
  • Naive double-submit: an unsigned cookie compared with a header. OWASP calls cookie-only validation insecure because a sibling subdomain or plain-HTTP response can set the cookie.
  • Trusting a missing Origin: treating absent or null Origin as same-origin to avoid breaking clients. OWASP says not to bypass validation for compatibility.
  • Relying on browser defaults: assuming Lax-by-default makes tokens unnecessary. Defaults differ by browser and the two-minute POST allowance leaves a window.
  • Suffix or regex origin matching: a check that accepts any origin ending in the company domain also accepts look-alike or taken-over hosts. Match the full origin from configuration.
  • Disabled Referer: a site-wide Referrer-Policy: no-referrer breaks Django's strict Referer check on HTTPS; use rel="noreferrer" on outbound links instead.

Framework mapping

FrameworkReferenceRelevance
OWASP ASVS 5.0V3.5.1, V3.5.2, V3.5.3, V3.3.2, V10.2.1Anti-forgery tokens or non-safelisted headers, preflight cannot be bypassed, no sensitive actions on safe methods or strict Sec-Fetch validation, SameSite by purpose, OAuth client CSRF
MITRE CWE / CAPECCWE-352, CWE-1275; CAPEC-62Weakness, improper SameSite attribute and attack pattern
NIST SP 800-53 Rev. 5SC-23 Session Authenticity; SI-10 Information Input Validation; SA-11 Developer Testing and EvaluationProtecting session authenticity, validating requests, testing
NIST CSF 2.0PR.PS-06Secure software development practices
ISO/IEC 27001:2022A.8.28 Secure coding; A.8.29 Security testing in development and acceptanceCoding standards and testing
CIS Controls v8.1Control 16 Application Software SecuritySecure development practices

Practitioner checklist

  • Confirm framework CSRF protection is enabled in every application and that each exemption has an owner and justification.
  • Remove all state changes from GET, HEAD and OPTIONS handlers.
  • Use synchroniser tokens or HMAC-signed, session-bound double-submit tokens; never the naive unsigned pattern.
  • Add a Fetch Metadata policy, rolled out in log-only mode, with Origin verification as fallback.
  • Reject simple content types on JSON APIs and never combine credentialed CORS with wildcard or regex origins.
  • Set session cookies __Host-, Secure, HttpOnly and explicit SameSite.
  • Protect login forms with a pre-session token and issue a new session ID after authentication.
  • Validate OAuth state or use PKCE on every authorisation code flow.
  • Require re-authentication for email, MFA, role and API key changes.
  • Test with WSTG-SESS-05 in more than one browser and add negative CSRF tests to CI.

Standards and references

Related NHI Mgmt Group resources: Web Application Attacks Guide · Security Headers Guide · Cross-Site Scripting (XSS) Prevention Guide · OAuth 2.0 and OpenID Connect Guide · Secure Coding Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Written and reviewed by Lalit Choda, NHI Mgmt Group. Last updated 24 October 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.