Stockio

CSRF Protection With SameSite Cookies, Tokens and Origins

Protect cookie-authenticated API mutations with CSRF tokens, origin validation, SameSite cookies, and clear middleware rules that survive real browser login flows.

By Chidi Nwosu

Backend Engineer ·

On this page

A browser automatically attaches eligible cookies to requests. That convenience creates a risk: another site may induce a visitor's browser to submit a request using an existing authenticated session. If the API accepts the mutation without checking user intent, the attacker can act through the victim's browser without knowing the session cookie.

Model which credentials are sent automatically

Cross-site request forgery targets requests that receive ambient credentials, especially cookies. Begin by listing every state-changing endpoint: account updates, billing changes, administrative actions, logout, and integration management. Include legacy form endpoints and alternate content types rather than checking only your newest JSON routes.

Use the API Latency Tester against a non-sensitive test endpoint to confirm statuses, but use an actual browser and controlled test pages for CSRF behavior. A standalone HTTP client does not reproduce cookie inclusion, SameSite rules, or browser navigation contexts.

The OWASP CSRF Prevention Cheat Sheet recommends established framework protections where available. Prefer those mechanisms to inventing a new token protocol, then verify how they interact with your API framework and frontend deployment.

Keep safe methods free of side effects

GET and HEAD should not change business state. Browsers, crawlers, preview tools, and prefetchers may invoke safe methods without a deliberate user action. A CSRF token added only to POST handlers cannot protect a destructive GET endpoint that remains reachable elsewhere.

Choose a consistent set of unsafe methods requiring protection, normally including POST, PUT, PATCH, and DELETE. Method alone does not prove risk, but it provides a reliable middleware boundary. Do not exempt a route simply because its UI is hidden or it usually receives AJAX requests.

Reject unexpected content types on JSON-only mutation endpoints. An attacker-controlled HTML form can send some content types without preflight; a permissive parser might interpret that input unexpectedly. Content-type restrictions are useful defense in depth, but should not be your sole CSRF control unless a carefully reviewed architecture explicitly supports that choice.

Separate machine-to-machine webhook routes from browser-session routes. A webhook normally uses its own signature authentication and does not rely on cookies. Exempting that specific route may be appropriate; exempting an entire API prefix because one webhook cannot supply a browser token is not.

Implement a proven token pattern

For a stateful application, a synchronizer token is a strong conventional choice. Generate a secret value associated with the authenticated session, expose it through a legitimate same-origin page or protected bootstrap response, and require it on mutations. Compare it using the framework's supported implementation.

const response = await fetch('/api/account/display-name', {
method: 'PATCH',
credentials: 'same-origin',
headers: {
  'Content-Type': 'application/json',
  'X-CSRF-Token': csrfTokenFromBootstrap
},
body: JSON.stringify({ displayName: 'Morgan' })
});

if (response.status === 403) {
showSessionOrSecurityError();
}

This browser example assumes the server verifies both the authenticated session and the CSRF token before applying the change. Supplying a header without verification creates no protection. The token should not be placed in a query string, where URLs can spread through logs, browser history, and referrers.

For stateless designs, use a carefully implemented signed double-submit approach bound to the authenticated context, as recommended by established guidance. A naive equality check between an attacker-injectable cookie and a request field can be vulnerable to cookie injection. Do not confuse a CSRF token with an authentication credential; both checks are needed.

Validate the Origin header for unsafe requests against an exact allowlist. Where your supported clients legitimately omit Origin, define a reviewed fallback, often involving Referer validation or an explicit non-browser authentication path. Do not silently treat every missing header as proof of trust.

Determine the target origin using trusted deployment configuration. Reverse proxy headers can help only when they are accepted exclusively from trusted proxies. Comparing against an attacker-controlled Host or forwarded header can undermine an otherwise sound check.

Set session cookies with Secure, HttpOnly, and an intentional SameSite policy. Strict may disrupt valid cross-site entry flows; Lax offers useful restrictions but is not a substitute for endpoint protections. Cross-site cookie use generally requires SameSite=None and Secure, bringing additional dependence on browser privacy policies.

Read MDN's Set-Cookie reference to understand these options. Same-site and same-origin are different concepts: related subdomains can be same-site while still representing distinct origins and different trust levels. A compromised sibling subdomain can therefore matter to your threat model.

Test real request paths and recovery behavior

Create controlled attacker pages attempting an ordinary form POST, a cross-origin fetch, and a navigation to any historical mutation URL. Confirm that rejected requests do not alter state, even when the attacker cannot read the response. Testing only whether JavaScript receives JSON misses the actual CSRF threat.

Check session lifecycle events. Tokens may change when a user logs in, logs out, switches accounts, or rotates a session. Decide how multiple tabs recover from a stale token without creating an infinite retry loop. A fresh bootstrap request may be appropriate, but never automatically repeat a potentially completed mutation without understanding its outcome.

CORS should not grant arbitrary origins credentialed read access to a token bootstrap endpoint. It is a separate control with separate semantics; the Fetch standard describes how browser credentials and origin policy interact. A correctly configured CSRF layer should fail closed when its token is missing or invalid.

Use the JSON Formatter to define a safe, consistent rejection body that contains no expected token value. Record deployment assumptions and route exemptions in your Backend & APIs documentation. Log bounded rejection reasons for diagnosis, but do not record CSRF tokens or whole session cookies. XSS can defeat many browser-side protections, so CSRF controls complement rather than replace script-injection defenses.

Work through an account-change test

Create two test accounts and sign into one in a browser profile dedicated to security testing. Record its current notification address. From a different origin that you control, submit a form targeting the address-change endpoint with a replacement address. The expected result is rejection and an unchanged account record, not merely an unreadable browser response. Repeat with a missing token, a token from the other account, and a valid token paired with a forbidden origin.

Include login CSRF in this exercise. An attacker may try to sign a victim into the attacker's account, causing later activity or sensitive uploads to land there. Review the framework's pre-authentication token handling and session rotation rather than assuming that unauthenticated endpoints never need protection. Logout deserves an explicit decision too: forced logout can disrupt work even though it does not directly disclose data.

Finally, inspect monitoring after these tests. A rejection event should identify the route, policy reason, and correlation identifier without capturing submitted secrets. Check that the same request cannot reach business logic through a trailing-slash alias or older API version. This turns a middleware unit test into evidence that the deployed routing boundary actually enforces the policy.

Key takeaways

  • Identify every cookie-authenticated mutation, including old forms and alternate routes.
  • Prefer framework-backed synchronizer tokens or a properly signed double-submit design.
  • Combine token checks with exact origin validation and intentional cookie attributes.
  • Test browser behavior, session transitions, and rejected-request side effects.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in Backend & APIs
Backend & APIs

12 Best Free Public APIs for Side Projects

Find approachable public APIs for weather, books, countries, and sample data, plus practical notes on access, limits, and choosing an endpoint for a side project.

Includes: API latencyChidi NwosuChidi Nwosu
Backend & APIs

10 Best Backend-as-a-Service (BaaS) Platforms

Compare ten backend services for app data, sign-in, and server functions, with plain-language strengths, trade-offs, and advice for choosing a platform that fits your project.

Includes: API latencyChidi NwosuChidi Nwosu
Backend & APIs

ETag Caching for APIs: Cache-Control and Redis

Build a safe API caching policy by separating HTTP freshness, conditional ETag validation, and Redis data reuse, with explicit rules for privacy and invalidation.

Includes: API latencyChidi NwosuChidi Nwosu
Backend & APIs

How to Fix CORS Preflight Errors in Your API

Diagnose CORS preflight failures with explicit origins, credential rules, OPTIONS responses, and cache behavior while keeping authentication and authorization separate.

Includes: API latencyChidi NwosuChidi Nwosu
Backend & APIs

API Rate Limiting With Token Buckets and Redis

Design API rate limits with token buckets, atomic Redis operations, fair identities, and Retry-After responses without confusing quotas with concurrency controls.

Includes: API latencyChidi NwosuChidi Nwosu
Backend & APIs

REST vs GraphQL vs tRPC: Choosing an API Style

Compare REST, GraphQL, and tRPC through client needs, caching, security, and deployment constraints, then validate your choice with a production-shaped prototype.

Includes: API latencyChidi NwosuChidi Nwosu