Laravel Sanctum 419 Error: A 5-Step Debugging Guide

php dev.to

Your Laravel API works.

Your credentials are correct.

/sanctum/csrf-cookie might even return 204.

Then you send the login request:

POST /login
Enter fullscreen mode Exit fullscreen mode

and Laravel responds with:

419 Page Expired
Enter fullscreen mode Exit fullscreen mode

If you've been changing random CORS settings hoping the error disappears, stop there.

A Laravel Sanctum 419 error is usually a CSRF/session authentication problem, and the fastest way to fix it is to identify exactly where the Sanctum authentication flow breaks.

Here's the debugging order I use.


TL;DR

For Laravel Sanctum SPA authentication, verify these five things:

1. Stateful middleware
2. SANCTUM_STATEFUL_DOMAINS
3. CORS credentials
4. Session cookies
5. Axios CSRF handling
Enter fullscreen mode Exit fullscreen mode

And always request:

GET /sanctum/csrf-cookie
Enter fullscreen mode Exit fullscreen mode

before:

POST /login
Enter fullscreen mode Exit fullscreen mode

If one piece of that chain is wrong, Laravel can respond with 419 CSRF token mismatch / Page Expired.


1. Understand What 419 Actually Means

When using Laravel Sanctum's SPA authentication, Laravel relies on normal session cookies and CSRF protection.

The browser flow looks roughly like this:

Frontend
   |
   | GET /sanctum/csrf-cookie
   v
Laravel
   |
   | Sets XSRF-TOKEN
   | Sets session cookie
   v
Browser
   |
   | POST /login
   | Cookie + X-XSRF-TOKEN
   v
Laravel validates request
Enter fullscreen mode Exit fullscreen mode

When Laravel cannot validate that CSRF/session relationship, you can end up with:

419 Page Expired
Enter fullscreen mode Exit fullscreen mode

So the question isn't simply:

"Is my CORS config correct?"

The better question is:

"At which point does the authentication chain break?"


2. Start With the Browser Network Panel

Before touching .env, Axios, or Laravel configuration, open:

Chrome DevTools → Network

Then perform the login again.

Find the first request that doesn't behave as expected.

Case A: OPTIONS fails

If the browser blocks an OPTIONS request, investigate CORS first.

Case B: /sanctum/csrf-cookie fails

Your browser isn't successfully initializing Sanctum's CSRF protection.

Case C: CSRF request succeeds but /login returns 419

This is where I'd inspect:

  • XSRF-TOKEN
  • Laravel session cookie
  • X-XSRF-TOKEN
  • cookie domain
  • Axios credentials
  • Sanctum stateful domains

Case D: Login succeeds but /api/user returns 401

Now the problem is more likely related to the authenticated session or Sanctum recognizing the request as stateful.

This simple classification eliminates a lot of unnecessary debugging.


3. Make Sure Sanctum Treats the SPA as Stateful

Modern Laravel applications can enable Sanctum's stateful API middleware in bootstrap/app.php.

For example:

use Illuminate\Foundation\Configuration\Middleware;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->statefulApi();
})
Enter fullscreen mode Exit fullscreen mode

This matters because Sanctum needs to understand that requests coming from your first-party SPA should use session authentication.

If you're following an older Laravel tutorial, you may see instructions involving:

app/Http/Kernel.php
Enter fullscreen mode Exit fullscreen mode

Be careful.

Laravel's application structure has evolved, so old Sanctum tutorials can send you to configuration locations that aren't appropriate for newer Laravel applications.


4. Check SANCTUM_STATEFUL_DOMAINS

Assume your setup looks like this:

Frontend:
https://app.example.com

API:
https://api.example.com
Enter fullscreen mode Exit fullscreen mode

Your SPA host needs to be recognized as stateful.

For example:

SANCTUM_STATEFUL_DOMAINS=app.example.com
Enter fullscreen mode Exit fullscreen mode

One common mistake is adding the protocol:

SANCTUM_STATEFUL_DOMAINS=https://app.example.com
Enter fullscreen mode Exit fullscreen mode

That's not what you normally want here.

Think host, not frontend URL.


localhost can be even trickier

Suppose Vite runs on:

http://localhost:5173
Enter fullscreen mode Exit fullscreen mode

Then you may need:

SANCTUM_STATEFUL_DOMAINS=localhost:5173
Enter fullscreen mode Exit fullscreen mode

Don't casually mix:

localhost
127.0.0.1
localhost:5173
127.0.0.1:5173
Enter fullscreen mode Exit fullscreen mode

Use one consistent development setup.

A surprising number of Sanctum bugs come from tiny hostname differences.


5. CORS Must Allow Credentials

If your SPA authentication uses cookies, your CORS configuration must allow credentialed requests.

A typical setup could include:

return [

    'paths' => [
        'api/*',
        'sanctum/csrf-cookie',
        'login',
        'logout',
    ],

    'allowed_methods' => ['*'],

    'allowed_origins' => [
        'https://app.example.com',
    ],

    'allowed_headers' => ['*'],

    'supports_credentials' => true,

];
Enter fullscreen mode Exit fullscreen mode

The important setting is:

'supports_credentials' => true,
Enter fullscreen mode Exit fullscreen mode

And don't casually do this:

'allowed_origins' => ['*']
Enter fullscreen mode Exit fullscreen mode

when you're relying on cookie credentials.

Specify the frontend origin explicitly:

https://app.example.com
Enter fullscreen mode Exit fullscreen mode

Origin and domain are not the same thing

For CORS:

https://app.example.com
Enter fullscreen mode Exit fullscreen mode

is an origin.

The scheme matters.

These are different origins:

http://app.example.com
https://app.example.com
Enter fullscreen mode Exit fullscreen mode

Meanwhile, Sanctum's stateful domain configuration is concerned with the appropriate host/domain.

Mixing these concepts is one reason Sanctum configuration feels confusing at first.


6. Inspect Your Session Cookie Configuration

Now suppose your architecture is:

app.example.com
api.example.com
Enter fullscreen mode Exit fullscreen mode

Both are under:

example.com
Enter fullscreen mode Exit fullscreen mode

A typical production session configuration might include:

SESSION_DOMAIN=.example.com
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax
Enter fullscreen mode Exit fullscreen mode

The parent-domain cookie configuration allows the session to work across the relevant subdomains.

After changing environment settings, also remember that cached configuration can make you think your changes aren't working.

Depending on your deployment workflow, refresh Laravel's configuration cache appropriately.

For example:

php artisan config:clear
Enter fullscreen mode Exit fullscreen mode

and rebuild/cache configuration according to your production deployment process.


7. Configure Axios Correctly

Your Laravel setup can be perfect and still fail if the frontend never sends the cookies back.

For Axios:

import axios from 'axios';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  withCredentials: true,
  withXSRFToken: true,
  headers: {
    Accept: 'application/json',
  },
});
Enter fullscreen mode Exit fullscreen mode

Before login:

await api.get('/sanctum/csrf-cookie');

await api.post('/login', {
  email,
  password,
});
Enter fullscreen mode Exit fullscreen mode

The two important Axios options are:

withCredentials: true
Enter fullscreen mode Exit fullscreen mode

and:

withXSRFToken: true
Enter fullscreen mode Exit fullscreen mode

Now look in the browser.

After:

GET /sanctum/csrf-cookie
Enter fullscreen mode Exit fullscreen mode

you should normally be able to see the relevant CSRF/session cookies.

Then inspect:

POST /login
Enter fullscreen mode Exit fullscreen mode

and confirm the authentication data is actually being sent back to Laravel.


A Better Way to Debug 419

Instead of changing Laravel, CORS, Axios, cookies, and middleware simultaneously, use this sequence.

Test 1 — CSRF endpoint

Request:

GET /sanctum/csrf-cookie
Enter fullscreen mode Exit fullscreen mode

Expected:

Successful response
+
XSRF/session cookies created
Enter fullscreen mode Exit fullscreen mode

If no cookies appear, don't debug your login controller yet.


Test 2 — Login request

Request:

POST /login
Enter fullscreen mode Exit fullscreen mode

Check:

Are cookies attached?
Is X-XSRF-TOKEN present?
Is the request being blocked by CORS?
Enter fullscreen mode Exit fullscreen mode

If the answer is no, you've narrowed the problem substantially.


Test 3 — Authenticated endpoint

After successful login:

GET /api/user
Enter fullscreen mode Exit fullscreen mode

If login succeeds but this gives:

401 Unauthenticated
Enter fullscreen mode Exit fullscreen mode

focus on:

Sanctum stateful configuration
Session cookie
Cookie domain
withCredentials
Middleware
Enter fullscreen mode Exit fullscreen mode

instead of CSRF.


419 vs 401 vs 422

These errors frequently get mixed together.

419

Usually investigate:

CSRF
XSRF token
Cookies
Session
Enter fullscreen mode Exit fullscreen mode

401

Usually investigate:

Authentication
Session recognition
Sanctum stateful requests
Missing credentials
Enter fullscreen mode Exit fullscreen mode

422

Usually investigate:

Validation errors
Missing fields
Invalid input
Enter fullscreen mode Exit fullscreen mode

Knowing the difference can save a surprising amount of time.


Why Does Sanctum Work in Postman but Fail in Chrome?

This is an extremely common situation:

Postman ✅
Browser ❌
Enter fullscreen mode Exit fullscreen mode

And it's an important clue.

Browsers enforce:

  • CORS
  • cookie policies
  • origins
  • credentials
  • SameSite behavior
  • CSRF flows

API clients such as Postman don't behave exactly like a browser page.

So if the API works from Postman but fails from React/Vue/Vite, start investigating the browser authentication flow, not your database credentials.


Don't "Fix" 419 by Disabling CSRF

You might find solutions online suggesting that you exclude your login endpoint from CSRF protection.

That can make the error disappear.

It doesn't necessarily mean you've fixed Sanctum.

For first-party SPA session authentication, the better solution is to correctly configure:

CSRF
Session cookies
Stateful domains
CORS
Frontend credentials
Enter fullscreen mode Exit fullscreen mode

rather than removing the protection that exposed the configuration problem.


One Production Architecture Detail People Miss

This setup:

app.example.com
api.example.com
Enter fullscreen mode Exit fullscreen mode

is very different from:

my-app.vercel.app
api.example.com
Enter fullscreen mode Exit fullscreen mode

In the first example, the frontend and backend share the same top-level site.

In the second, they're on unrelated domains.

CORS cannot magically turn unrelated domains into the same first-party cookie environment.

So if you're struggling with Sanctum cookie authentication across completely different production domains, revisit the architecture itself.

A custom frontend domain such as:

app.example.com
Enter fullscreen mode Exit fullscreen mode

can make a first-party Sanctum setup considerably cleaner.


My 60-Second Sanctum 419 Checklist

Before searching Stack Overflow again, check this:

[ ] statefulApi() enabled

[ ] SPA exists in SANCTUM_STATEFUL_DOMAINS

[ ] Exact frontend origin allowed by CORS

[ ] supports_credentials = true

[ ] Correct SESSION_DOMAIN

[ ] Secure cookie configuration matches HTTPS setup

[ ] Axios withCredentials = true

[ ] Axios withXSRFToken = true

[ ] /sanctum/csrf-cookie called before login

[ ] XSRF-TOKEN appears in browser

[ ] Session cookie appears in browser

[ ] Login request sends cookies

[ ] X-XSRF-TOKEN is sent correctly
Enter fullscreen mode Exit fullscreen mode

If all twelve are correct, you've eliminated most of the common causes of a Laravel Sanctum 419 error.


The Debugging Order Matters More Than the Fix

The biggest lesson isn't a specific .env setting.

It's this:

Debug the first broken request instead of randomly changing authentication configuration.

Use this order:

OPTIONS
   ↓
/sanctum/csrf-cookie
   ↓
Cookies
   ↓
POST /login
   ↓
/api/user
Enter fullscreen mode Exit fullscreen mode

Find where the expected behavior stops.

That's usually where your real problem begins.


Need the Full Laravel Sanctum 419 Reference?

I wrote a more detailed guide covering Laravel Sanctum CORS, CSRF cookies, Axios configuration, production domains, localhost issues, session configuration, 401 vs 419 vs 422 errors, and deployment troubleshooting.

👉 Read the complete Laravel Sanctum CORS 419 Error Fix guide

Use the full guide when the quick checklist above doesn't expose the problem immediately.


If you're debugging Sanctum today, remember:

419 is the symptom. The browser's request flow tells you the cause.

Source: dev.to

arrow_back Back to Tutorials