Safeguard
Developer Tools•

CSRF protection for JSON APIs: custom headers and what to check

A JSON API that uses cookie auth can still be forged. Learn how a required custom header, strict CORS and token binding protect it, and where this breaks.

Safeguard Security Team
3 min read

A JSON API that authenticates with cookies is still exposed to CSRF. A reliable fix is to require a custom request header, such as X-CSRF-Token, on every state changing call and to keep your CORS allowlist tight. Browsers then preflight cross-origin calls, and your server refuses the ones it does not approve.

Why JSON endpoints are not automatically safe

Teams often assume that because the body is JSON, an HTML form cannot send it. That is only partly true. Forms can send form encoded, multipart or plain text bodies. If your server parses plain text as JSON, or ignores the content type, a form can still deliver a body that your handler accepts.

The first fix is simple: reject any request whose content type is not application/json. A JSON content type is not a safelisted type, so a cross-origin call using it triggers a preflight.

The custom header pattern

OWASP describes this as a control for endpoints that cannot use form tags. The flow is short.

  1. Your front end adds a header to every request that changes state.
  2. The server rejects requests missing the header.
  3. A page on another origin cannot add the header without a preflight, and your CORS policy does not approve it.

The header value can be a token. OWASP prefers header delivery for synchronizer tokens over hidden fields where JavaScript is involved, and says not to put tokens in URLs because URLs leak through history, logs and referrer headers.

Pick a token style

  • Stateful sessions: a synchronizer token stored in the session, compared on each request.
  • Stateless services: a signed double submit cookie. OWASP recommends an HMAC computed over the session ID and a random value, sent as a header or form field. Signing alone is not enough. Bind the token to the authenticated session.
  • Avoid the naive version that just checks that a cookie equals a request value. An attacker who can write cookies can defeat it.

Where this breaks

  • Permissive CORS. OWASP warns that any cross-origin request from an allowed domain can set custom headers. Allowing every subdomain is risky if one is taken over.
  • XSS. Script in your origin can read the token and send valid requests, so the control relies on XSS prevention.
  • GET with side effects. A header requirement means nothing if a link can trigger a change.
  • Non browser clients. Mobile or server clients do not suffer CSRF the same way. If they use bearer tokens in headers rather than cookies, the browser never attaches credentials automatically.

A short implementation checklist

  1. Accept only application/json on write endpoints.
  2. Require the header and compare it on the server.
  3. Set the session cookie with Secure, HttpOnly and SameSite.
  4. List exact origins in CORS, with Vary: Origin.
  5. Move every state changing GET to a write method.
  6. Test it: send a write request from a different origin and confirm it fails.

Quick check

Is a custom header enough on its own?

OWASP advises at least one defence in depth control as well, such as SameSite cookies or Origin and Referer checks.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.