Why am I getting a CSRF token mismatch with Laravel and Sanctum?

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Why am I getting a CSRF token mismatch with Laravel and Sanctum? A Deep Dive into SPA Authentication

As a senior developer working with modern full-stack applications, dealing with cross-origin authentication—especially when mixing session-based mechanisms like Laravel Sanctum with Single Page Applications (SPAs) built on frameworks like React—often introduces subtle but frustrating bugs. One of the most common culprits is the dreaded CSRF token mismatch.

If you are running into this issue while using Laravel Sanctum, it usually points to a misalignment in how cookies, session state, and cross-origin requests are being handled between your frontend (e.g., React on port 3000) and your backend (e.g., Laravel on port 8000).

Let's dissect the setup you described, analyze the configuration points, and uncover why this mismatch occurs, along with the practical steps to fix it.

Understanding the Sanctum/CSRF Mechanism

Laravel Sanctum leverages session-based authentication for SPAs. When a user logs in, Laravel sets a session cookie (which contains the CSRF token) on the client side. For subsequent authenticated requests, the browser automatically sends this cookie back with the request, allowing the backend to verify the stateful nature of the request via middleware like VerifyCsrfToken.

The mismatch occurs when the mechanism that establishes and verifies this shared state is broken across the origin barrier. The core tension here is between standard Cross-Origin Resource Sharing (CORS) policies and session cookie transmission.

Analyzing Your Configuration for Potential Issues

Let's review the specific configurations you provided, as these are often the source of the conflict:

1. Frontend Request Pattern

Your frontend correctly initiates the process by hitting sanctum/csrf-cookie first:

return apiClient.get('sanctum/csrf-cookie', config).then(response => {
    // This request sets the necessary session cookie on the browser.
    apiClient.post('/api/v1/login', JSON.stringify(credentials));
});

This initial step is crucial; it forces the browser to establish the session context before attempting the actual login. The use of withCredentials: true in Axios is also correctly implemented, signaling that cookies should be sent with subsequent requests.

2. API Middleware and Statefulness

Your API middleware setup includes:

\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
// ...
\App\Http\Middleware\VerifyCsrfToken::class,

The EnsureFrontendRequestsAreStateful middleware is key, as it tells Sanctum to expect stateful requests (via cookies). The issue often surfaces when the browser fails to consistently send the session cookie with every request due to stricter CORS settings.

3. Session Domain and CORS Configuration

Your session configuration suggests a potential point of friction:

SESSION_DOMAIN=http://localhost:3000

And your CORS middleware allows all origins:

'allowed_origins' => ['*'],
// ...
'supports_credentials' => true,

While supports_credentials: true and allowed_origins: ['*'] are necessary for cross-origin communication, the specific way Laravel handles session cookies across domains can be fragile if not explicitly defined. The mismatch often stems from the browser security model blocking or modifying the cookie when the origin headers don't perfectly align with the session domain expectations.

The Solution: Ensuring Consistent Cross-Origin Trust

The most reliable fix involves ensuring that the session setup is maximally permissive yet secure, and confirming that the CORS headers are correctly instructing the browser to permit credentials.

Best Practice Implementation

  1. Refine SESSION_DOMAIN (If necessary): If you are strictly running on localhost, ensure your SESSION_DOMAIN reflects the exact scheme and port being used by the frontend, as you have done: http://localhost:3000. This tells Laravel which origins are allowed to set/receive session cookies.

  2. Verify CORS Headers: Ensure that your CORS configuration explicitly supports credentials, which you have done (supports_credentials: true). The interaction between Sanctum's stateful checks and the browser's handling of withCredentials is where most errors hide.

  3. Check Cookie Security (HTTPS/Mixed Content): Although you are using http://, ensure your development setup doesn't introduce mixed-content issues, which could cause the cookie to be rejected by the browser entirely.

For robust API security, always refer to the official documentation and best practices provided by the framework itself. For deeper insights into Laravel architecture and security features like Sanctum, exploring resources from https://laravelcompany.com is highly recommended.

Conclusion

The CSRF token mismatch in a Sanctum SPA setup is rarely about missing code; it's usually an interaction failure between session state management (cookies) and cross-origin policies (CORS). By carefully reviewing your SESSION_DOMAIN, ensuring supports_credentials is true, and correctly sequencing the initial request to fetch the CSRF cookie, you can resolve this issue. Remember, in full-stack development, understanding how the browser manages state across origins is just as important as writing perfect backend code.