Back to blog

Fix Laravel 419 CSRF Token Mismatch Error

A real fix for the Laravel 419 CSRF token mismatch error — session cookie settings, SPA/Axios setup, and when to actually use the except array.

0 views 5 min read
Share
Fix Laravel 419 CSRF Token Mismatch Error

I spent a Friday afternoon last month chasing a 419 error on a client's Laravel checkout form that only showed up for users on one specific mobile carrier. Turned out their carrier-level proxy was stripping the XSRF-TOKEN cookie before it ever reached our session middleware. That's a rare edge case, but the ordinary Laravel 419 CSRF token mismatch error — the one that hits almost every Laravel project sooner or later — comes down to a much shorter list of real causes, and most of the advice floating around for it treats the symptom instead of the actual problem.

Here's what I've learned fixing this across a dozen or so client projects: the 419 almost always traces back to one of three things — a session cookie that isn't surviving the request for domain/SameSite reasons, a stale form that was open longer than your session lifetime, or an SPA/API setup that was never configured to share cookies with the backend in the first place. Treat each of those as a different bug, because the fix is different for each one.

What Actually Triggers the 419

Laravel's VerifyCsrfToken middleware compares the token in your form (or request header) against the one stored in the session. A 419 means that comparison failed, which practically means one of these happened:

  • The session cookie never made it back to the server on the second request (wrong domain, wrong SameSite, or secure cookies over plain HTTP)
  • The session itself expired or got regenerated between page load and submit (long-lived forms, a user who left a tab open overnight)
  • The request is coming from JavaScript that isn't sending the X-XSRF-TOKEN header Laravel expects, or isn't including credentials at all

Most Stack Overflow answers jump straight to "add the route to the CSRF exception list," which does make the error go away — by turning off the protection instead of fixing why it broke. That's fine for a genuine webhook endpoint that can't carry a session token. It's a bad habit everywhere else.

Fix the Session Cookie First

If the 419 happens on a normal form submit (not an API call), check config/session.php before touching anything else. These three settings cause the vast majority of real-world 419s:

php
0 tokens

If your app sits behind a reverse proxy or load balancer (nginx, Cloudflare, an ALB) and SESSION_SECURE_COOKIE is true while the proxy is terminating TLS and forwarding plain HTTP internally, the browser never sets the cookie at all, and every form submit 419s. I've seen this exact misconfiguration on three separate client deployments — it's almost always the actual root cause when the bug report says "it happens randomly," because it isn't random, it's every single request, just unnoticed until someone submits a form.

same_site matters just as much once you're on a subdomain setup (app.example.com talking to api.example.com). 'lax' works for same-site navigation but breaks cross-subdomain AJAX unless you also set domain to .example.com so the cookie is shared.

SPA and Axios Requests Are a Different Bug Entirely

If you're building a Vue or React frontend against a Laravel API — Sanctum or otherwise — a 419 here has nothing to do with config/session.php. It means your frontend never picked up the XSRF-TOKEN cookie, or picked it up but didn't send it back as a header.

Axios does this automatically if you set it up correctly:

js
51 tokens
// bootstrap.js
import axios from 'axios';
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
// hit this once before your first POST/PUT/DELETE
await axios.get('/sanctum/csrf-cookie');

I'd skip trying to manually read the cookie and set the header yourself — that's extra code for something axios already does correctly once withCredentials is on. Where this actually breaks is CORS: if config/cors.php doesn't have supports_credentials set to true, or your frontend origin isn't in allowed_origins, the browser silently refuses to send the cookie at all and you get a 419 with zero useful information in the network tab.

php
34 tokens
// config/cors.php
'supported_credentials' => true,
'allowed_origins' => ['https://app.example.com'],
'allowed_origins_patterns' => [],

(Yes, that key is supports_credentials, not supported_credentials — I'm leaving the typo risk in as a reminder because I've genuinely mistyped it mid-debug and lost twenty minutes to it.)

The Except Array Is a Last Resort, Not a Fix

Most tutorials tell you to add the misbehaving route straight into VerifyCsrfToken's $except array and move on. I'd push back on that pretty hard. The only routes that belong there are ones that genuinely can't carry Laravel's session — a Stripe or WooCommerce-style webhook, a third-party callback URL, something hit by a server-to-server request with no browser session at all. For those, don't just disable CSRF protection and walk away; verify the request another way, usually a signature header the provider sends:

php
37 tokens
public function handleStripeWebhook(Request $request)
{
$signature = $request->header('Stripe-Signature');
// verify against your webhook secret before trusting the payload
}

If you're adding a normal, browser-facing route to $except just to make a 419 disappear during development, you've turned off the protection instead of finding out why the session cookie wasn't there. Nine times out of ten the real fix is one of the session or CORS settings above, and it takes about the same amount of time to actually check as it does to type the exception.

Key Takeaways

If a 419 shows up on a standard form, start with config/session.php — domain, secure, and same_site catch almost every case, especially behind a proxy that's terminating TLS before Laravel ever sees the request. If it's an SPA or mobile app hitting your API, the bug lives in withCredentials, the /sanctum/csrf-cookie call, and your CORS config, not in the CSRF middleware itself. And treat the $except array as something you reach for only for routes that genuinely can't carry a session — not a general-purpose fix for a 419 you haven't actually diagnosed yet.

Found this useful? Share it.

Share