All posts
securitycsrf

Fixing CSRF Token Mismatch Errors

Why CSRF token mismatch errors happen, common causes from session handling to SPA architecture, and how to fix them correctly.

SR

Suhail Roushan

August 6, 2026

·
5 min read
·
0 views

A CSRF token mismatch error is your CSRF protection doing exactly its job — rejecting a request that either doesn't carry the expected token, carries a stale one, or carries one that doesn't match the session it's tied to — and the fix requires understanding what's actually breaking the token/session relationship, not just suppressing the check.

This error means the CSRF token submitted with a request doesn't match the token the server expects for that session — this protection exists specifically to prevent cross-site request forgery, where a malicious site tricks a user's authenticated browser into submitting a request to your application without the user's intent.

Why This Error Happens

CSRF protection works by issuing a token tied to the user's session, embedding it in forms or requiring it in headers for state-changing requests, and rejecting any request that doesn't include a matching token. A mismatch happens when the token submitted doesn't correspond to the current session — commonly because the session expired or rotated between when the token was issued and when the form was submitted, the token wasn't properly included in an AJAX/fetch request, or a caching layer served a stale page with an outdated token.

Reproducing and Diagnosing the Error

A common SPA-related cause — CSRF token not included in an API request:

// Missing CSRF token — will fail server-side validation for state-changing requests
await fetch("/api/account/update", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "New Name" }),
});

A session/token expiry mismatch — user leaves a form open too long:

1. User loads form at 10:00am, receiving CSRF token A tied to session A
2. Session expires or rotates at 10:30am (e.g., after a token refresh)
3. User submits form at 10:35am with token A, but the server now expects
   a token matching the newer session — mismatch

Core Concepts Behind This Error

CSRF tokens are tied to a session, not just issued globally — a mismatch after session rotation, logout/login, or expiry is often not a bug but the protection correctly rejecting a token that no longer corresponds to the active session, which is exactly the scenario CSRF protection is designed to catch.

Single-page applications need to explicitly fetch and include the CSRF token in every state-changing request header, since there's no traditional form submission automatically carrying a hidden token field — this requires deliberate client-side handling, typically reading the token from a cookie or a dedicated endpoint and attaching it to request headers.

Caching (browser cache, CDN cache, or reverse proxy cache) serving a stale page can serve an outdated CSRF token embedded in that page's HTML, causing every subsequent submission from that cached page to fail — pages containing CSRF tokens generally need to be excluded from caching or have their tokens refreshed independently of the page cache.

The double-submit cookie pattern and the synchronizer token pattern are the two common CSRF protection implementations, and they have different failure modes — double-submit compares a cookie value against a submitted value (failing if cookies are blocked or misconfigured), while synchronizer tokens compare against server-side session state (failing on session mismatches specifically).

Fixing CSRF Token Mismatch Errors

Fix 1: For SPAs, explicitly fetch the current CSRF token and include it in every state-changing request:

async function apiRequest(url: string, body: unknown) {
  const csrfToken = document.cookie.match(/csrf_token=([^;]+)/)?.[1];
  return fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-CSRF-Token": csrfToken ?? "",
    },
    body: JSON.stringify(body),
  });
}

Fix 2: Exclude pages containing CSRF tokens from aggressive caching, or refresh the token independently via a lightweight endpoint if the page itself must be cached:

res.setHeader("Cache-Control", "no-store");

Fix 3: Handle session expiry gracefully on the client, detecting a CSRF mismatch specifically and prompting a refresh rather than showing a generic error — since a mismatch after a long-idle form is a legitimate, expected scenario, not necessarily an attack:

const response = await apiRequest("/api/account/update", data);
if (response.status === 403) {
  const error = await response.json();
  if (error.code === "CSRF_MISMATCH") {
    // refresh the page or re-fetch a new token before allowing resubmission
    await refreshCsrfToken();
  }
}

Is a CSRF Mismatch Always a Sign of an Attack?

No — the overwhelming majority of CSRF mismatch errors in practice are legitimate session/token lifecycle issues (expired sessions, stale cached pages, missing token handling in a new client integration), not actual attack attempts. Treat a mismatch as a signal to check your token issuance, session handling, and client-side inclusion logic first; investigate as a potential attack only if the pattern correlates with other suspicious signals (unusual request origins, volume, or timing).

Preventing CSRF Token Mismatch Errors in Production

Ensure every client integration (particularly SPAs and mobile app backends) explicitly fetches and includes CSRF tokens in state-changing requests, since this is the most common source of mismatches for API-driven frontends. Exclude CSRF-token-bearing pages from caching layers, and handle expected mismatch scenarios (session expiry on long-idle forms) with a graceful refresh-and-retry flow rather than a confusing generic error shown to the user.

If you're seeing CSRF mismatches, check first whether it's a client-side token inclusion gap or a caching issue before assuming an attack — those two causes account for the large majority of real-world CSRF error reports.

Related posts

Written by Suhail Roushan — Full-stack developer. More posts on AI, Next.js, and building products at suhailroushan.com/blog.

Get in touch