Audit BenchAi
← All posts

Reviewing CSRF Protection in Web Applications

·7 min read

Cross-site request forgery exploits a basic fact about how browsers work: a browser attaches a site's cookies to a request no matter which page triggered that request. If a logged-in user visits a malicious page that quietly submits a form to your application, the browser sends the user's session cookie right along with it. CSRF gets less attention now that modern cookie defaults help contain it, but "less attention" and "solved" are not the same thing.

What actually needs CSRF protection

Any request that changes state — creates, updates, or deletes something — and authenticates using a cookie the browser sends automatically needs protection. That covers most POST, PUT, PATCH, and DELETE endpoints in a typical application. A GET request that changes state is a separate problem worth flagging on its own — GET requests should be safe to prefetch, cache, and link to without side effects, CSRF risk or not.

Why SameSite cookies reduce the risk but don't eliminate it

Modern browsers default new cookies to SameSite=Lax, which blocks most cross-site requests from carrying the cookie and has genuinely reduced how often CSRF gets exploited in the wild. It is not a complete fix. Lax mode still allows the cookie on a top-level GET navigation, which matters if a state-changing action can be triggered by GET. Older browsers and some in-app webviews don't enforce SameSite at all. And an application that authenticates through more than one mechanism — a session cookie with a bearer-token fallback, for example — can still be exposed through whichever path doesn't rely on the cookie being blocked.

Signs CSRF protection is missing or only looks present

  • A state-changing endpoint with no anti-CSRF token check at all, relying solely on the session cookie being present.
  • A token that is checked for existence — "is a token field present" — without actually verifying its value against the one issued for that session.
  • A CSRF token passed in the URL of a GET-triggered action, where it can leak through browser history, server logs, or the Referer header sent to third-party resources on the page.
  • A double-submit-cookie implementation that compares the cookie value and the submitted value with a plain equality check instead of a constant-time comparison, or that doesn't tie the token to the session at all.

APIs are not automatically exempt

It is tempting to assume CSRF is a "web forms" problem and that a JSON API is naturally immune. That is only true if the API authenticates purely through a header the browser won't attach automatically, like a bearer token read from local storage. An API that authenticates via a browser-managed session cookie — common in single-page applications that share a domain with their backend — is exactly as exposed to CSRF as an HTML form, regardless of the request body being JSON instead of form-encoded fields.

Checklist for reviewing state-changing endpoints

  1. Every state-changing request that relies on cookie-based authentication requires and verifies an anti-CSRF token, or is otherwise proven unreachable cross-site.
  2. No state-changing action is reachable via a plain GET request.
  3. CSRF tokens are transmitted in the request body or a custom header, never in a URL that could end up in logs, history, or a referrer.
  4. Cookie-authenticated APIs get the same CSRF review as HTML form submissions — the response format doesn't change the underlying browser behavior.
  5. SameSite cookie attributes are set deliberately, not left at whatever the framework or library defaults to.

Read about reviewing API changes for security risk →

See how to review broken access control before it ships →