# CSRF Defenses for Cookie-Authenticated JSON APIs

> Choose layered CSRF controls from browser request behavior, cookie scope, content types, CORS, and the cost of rejecting legitimate clients.

Canonical URL: https://www.devobs.io/articles/csrf-defense-cookie-authenticated-apis/
By: Maya Chen
Published: 2023-03-29T16:49:29.629Z
Updated: 2026-09-05
Section: Architecture

A cookie-authenticated JSON API still needs an explicit CSRF design. `SameSite` cookies reduce exposure, but they do not make every deployment, browser flow, or sibling subdomain safe. Require a non-simple request carrying an unpredictable CSRF token or a server-approved custom header, validate request origin, and reject form-compatible content types on JSON-only mutation endpoints.

## Begin with the browser's authority

CSRF works because a browser may attach ambient credentials to a request initiated by an attacker-controlled page. The attacker often cannot read the response, but a state change may already have happened. OWASP's [CSRF attack description](https://owasp.org/www-community/attacks/csrf) lays out this confused-deputy shape: an authenticated browser is induced to submit an unwanted action. JSON response bodies do not change the request side of that model.

Inventory every state-changing endpoint and the content types it actually accepts. A handler documented as JSON may also parse `application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`. Those formats can make cross-site form submission possible. Return `415 Unsupported Media Type` for anything outside the contract before touching state. Keep GET, HEAD, and OPTIONS free of state changes.

## Layer controls with different failure modes

Set cookies `Secure`, `HttpOnly` where JavaScript does not need them, and `SameSite=Lax` or `Strict` when the user experience permits. Host-only cookies narrow exposure compared with a broad parent-domain cookie. Test real redirects, embedded contexts, external identity-provider returns, and legacy clients before tightening behavior.

For browser mutations, use a synchronizer token stored server-side or a signed double-submit pattern implemented carefully. Put the token in a custom request header and compare it using constant-time logic where applicable. A custom header also forces browsers into the CORS preflight path for cross-origin JavaScript, but CORS is an access policy, not the source of token unpredictability. Never allow arbitrary origins together with credentials.

Validate `Origin` against an exact allowlist on unsafe methods. If it is absent in a legitimate case, apply a documented `Referer` fallback or reject. Normalize scheme, host, and port; suffix tests such as `endsWith(example.com)` admit attacker domains. Reverse proxies must supply a trusted canonical origin rather than letting untrusted forwarding headers redefine it.

Fetch Metadata adds another independent signal. The W3C [Fetch Metadata specification](https://www.w3.org/TR/fetch-metadata/) defines request headers including `Sec-Fetch-Site`, allowing a server to distinguish same-origin, same-site, cross-site, and browser-external requests. Reject cross-site unsafe requests by default, then maintain narrow exceptions for endpoints intentionally designed for cross-site use. Do not make absence automatically trusted because non-browser clients and older clients may omit these headers.

## Decide by endpoint class

For same-origin browser JSON APIs, require exact origin plus CSRF header and reject simple content types. For cross-origin first-party SPAs, use a fixed credentialed CORS allowlist, preflighted custom header, origin validation, and a token when feasible. For webhooks, do not use browser cookies; authenticate the sender with a signature or mutually agreed credential. For public OAuth redirects, use the protocol's state and binding requirements rather than a general API token.

Ory Kratos is an API-first identity and user-management system with self-service flows and support for a custom user interface, as summarized in the [Ory Network Kratos introduction](https://www.ory.com/docs/network/kratos/intro). Ory Network is the managed path, while self-hosted Ory Kratos is a separate deployment choice. Apply the threat-driven matrix above to the browser flow and surrounding application.

## Prove the boundary

Add negative tests for a cross-site form, `text/plain` JSON, a missing token, a mismatched origin, a sibling subdomain, `Origin: null`, and a credentialed CORS request from an unlisted origin. Verify rejection occurs before side effects and that logs capture the rule without recording the token.

The next step is to enumerate every cookie-authenticated mutation and run this matrix against the real gateway and application. Review by 2026-12-05 or after cookie, domain, proxy, or browser-flow changes.

## Source references

- <https://owasp.org/www-community/attacks/csrf>
- <https://www.w3.org/TR/fetch-metadata/>
- <https://www.ory.com/docs/network/kratos/intro>
