Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

faust's avatar
Level 1

419 CSRF Mismatch Token error

Hello

I'm facing an issue with CSRF tokens in my SPA. I'm using Sanctum for authentication and everything works just fine. The problem arises after i log in. All subsequent requests return a 419 CSRF Token Mismatch error. It goes away only after refreshing the page, and when i logout and try to log in again, i get a 419 and have to refresh again.

I have tried all the solutions i found online but nothing worked. I tried setting up an axios interceptor to request another token when getting a 419 error but that create an infinite loop of 419's.

I have my Vue files in my Laravel's resources/js folder, not as a seperate project.

Kernel.php

    protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Illuminate\Http\Middleware\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

cors.php

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

    'allowed_methods' => ['*'],

    'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:8000')],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => true,

sanctum.php

'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
        '%s%s%s',
        'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1,127.0.0.1:5173',
        env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '',
        env('FRONTEND_URL') ? ','.parse_url(env('FRONTEND_URL'), PHP_URL_HOST) : ''
    ))),
'middleware' => [
        'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
        'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
    ],

resources/js/bootstrap.js

import axios from 'axios';

axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.headers.common['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
axios.defaults.withCredentials = true;
axios.defaults.baseURL = "http://localhost:8000";

stores/auth.js

        async getToken() {
            await axios.get("/sanctum/csrf-cookie");
        },
        async getUser() {
            await this.getToken();
            const data = await axios.get("/api/user");
            this.authUser = data.data;
        },
        async handleLogin(data) {
            this.authErrors = [];

            await this.getToken();

            try {
                await axios.post('/login', {
                    email: data.email,
                    password: data.password
                })
                    .then(async () => {
                        this.getUser();
                    });

                this.router.push({ name: 'home' });
            } catch (error) {
                if (error.response.status === 422) {
                    this.authErrors = error.response.data.errors
                }
            }
        },

api.php

Route::middleware(['auth:sanctum'])->get('/user', function (Request $request) {
    return $request->user();
});

auth.php

Route::post('/register', [RegisteredUserController::class, 'store'])
                ->middleware('guest')
                ->name('register');

Route::post('/login', [AuthenticatedSessionController::class, 'store'])
                ->middleware('guest')
                ->name('login');

Route::post('/forgot-password', [PasswordResetLinkController::class, 'store'])
                ->middleware('guest')
                ->name('password.email');

Route::post('/reset-password', [NewPasswordController::class, 'store'])
                ->middleware('guest')
                ->name('password.store');

Route::get('/verify-email/{id}/{hash}', VerifyEmailController::class)
                ->middleware(['auth', 'signed', 'throttle:6,1'])
                ->name('verification.verify');

Route::post('/email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
                ->middleware(['auth', 'throttle:6,1'])
                ->name('verification.send');

Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])
                ->middleware('auth')
                ->name('logout');

.env

APP_URL=localhost:8000
FRONTEND_URL=http://localhost:8000
SANCTUM_STATEFUL_DOMAINS=localhost:8000
SESSION_DOMAIN=localhost

SESSION_DRIVER=cookie

Sample of 419 error

Status
419
unknown status
VersionHTTP/1.1
Transferred13.43 kB (11.94 kB size)
Referrer Policystrict-origin-when-cross-origin
DNS ResolutionSystem

	
Access-Control-Allow-Credentials
	true
Access-Control-Allow-Origin
	http://localhost:8000
Cache-Control
	no-cache, private
Connection
	close
Content-Type
	application/json
Date
	Wed, 30 Aug 2023 08:38:18 GMT
Date
	Wed, 30 Aug 2023 08:38:18 GMT
Host
	localhost:8000
Set-Cookie
	laravel_session=eyJpdiI6InczOUNCZ2JZQVgyTTFsN2RCMGxSeGc9PSIsInZhbHVlIjoicm15aHNudEtDZTUySy9lcjNJWHdvaUljeklreFg1bG9RZTQ4R2hNY1l2Umo1UGQxZnJreU9kNXZzbkZobUdVanJHc3p2Wmx4OGxuYm9YZWZoV1E1T0ZnNlNuWVU3alNmOStybW5FY2g2MHpGL2hFVmxhR2tmbktkM092L3dweGQiLCJtYWMiOiI2NmMzN2QwYmUxYTIzMGFjYmRjOGExODMyYWY5YzFjM2I4NDhmOTAzYzcyZWM5NGE2Y2FjMDk4MTdiOTc3NGYyIiwidGFnIjoiIn0%3D; expires=Wed, 30 Aug 2023 10:38:18 GMT; Max-Age=7200; path=/; domain=localhost; httponly; samesite=lax
Set-Cookie
	U7GnRuqGTit8GEay7bCk5pNXATOfyX4VwFOxUDFo=eyJpdiI6IlhPaXRVQTlNajZaNGZzQ0hFMTJ2cVE9PSIsInZhbHVlIjoiL1lMWEFaRUtNdkRHc3hPejNhaFRvUHB5RUEwQjF2K2hEd1hrdlRQWWt0ZFYyemw0NDFWbkxXNGlKVVFxaEFRTjM3dENPU1UwcnVySWhCbHE0ZHNUUjBBQXp5UW1TSmJZQzlHU1MzdlZQN1BqUFpucE1xNXlaTmU0MWc1NHdhSHN1am5JL3ZsL2pCZEhSMzlGZy9GeU9XaWZUNWtXZk1henU3Mm14TUFmVnI5aDJnbzRUWEJINnVCNGFGRSt0YjRWbk5idWNaeGp0dFNGUnZaVEtBaDRheUtIem1MT3VvdVFGNW5iMzRZcUlld1FZWnlHRmJ4My92Wnp5KzM2OXJaNVZjTXlPYjJ0RGRybTZySG5ldkhBeFE9PSIsIm1hYyI6Ijc4ZmMzZWQ5NGEzYWNjMGRkMjUwNzliYWU3ZjY0NWQyZjYyNzU3MjgyMjNiNDczY2JhNmI0NTI1YTk0NTg4ODQiLCJ0YWciOiIifQ%3D%3D; expires=Wed, 30 Aug 2023 10:38:18 GMT; Max-Age=7200; path=/; domain=localhost; httponly; samesite=lax
X-Powered-By
	PHP/8.1.6
	
Accept
	application/json, text/plain, */*
Accept-Encoding
	gzip, deflate, br
Accept-Language
	en-US,en;q=0.5
Cache-Control
	no-cache
Connection
	keep-alive
Content-Length
	48
Content-Type
	application/json
Cookie
	XSRF-TOKEN=eyJpdiI6ImNXTzh3TE95c0YvMkJJMTU0TTZ1VXc9PSIsInZhbHVlIjoiYVJUclpjbmFQZjZzQjYxNUtUZTBQamtBOHFiRzczSXN5cG1kMmlXSk4vZnR4MDRwSDdJWjd2L29teURRTm1CU1JGOElScGZCMmNReGJHOFhtcXNCZ2VmRXhyRnhKajZEK3ozSzdTdW96Y1lPYmo0NGVRL0FlNENrU1FPRHRtSTciLCJtYWMiOiI2NDY1MTIxNzE1ZDdjMjEyY2E5YzQ4ODYzYjk2ZmE1MWViNGJmYzY2OTZlY2MxODgzZWRlMDZiMzNjYzk0MzIwIiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6Ikdkdmw4ODFwMm1zR2xOQ1UwRHJ6b2c9PSIsInZhbHVlIjoiVUhNeDBWeEppYU1kcy9XOTd5U0hzYUNRVEpJRmFWeFpZTnZXQXhMS0dBU052YURuTUd4RnlPeEdLcXA1Z1RxN…Gc9PSIsInZhbHVlIjoiM2c0MURKQnoxc3ltMU9mdlZjamJBcFVYSCtBUEZHRStEaGlaaUlRbjI0MGEyQ2s5Tnk3Nzh3NmRhSHhtb0JWa1ZMcUcyQk5MeTNwWW5kZ0FXSktKdFBEeUhZSWFTZWptaGNBZG1ZT05JNkpmVGxvNnBVWUN1OG5GbzRidTRsTjJCaUNKc0dUaGJiUUtITDIyUHZSUUpQbWtuMSsyTlZrVjBqUkNLUHRUZ1YzSWEydm11bGFjVGkyVUhxWTZzV3IzSTI5S2JCTzdENWFSdWJ1aEZ0dElCdmtUZnJVa1BEYjEzd3Z4S3AzVEZJRURiZzVpSUJtZ0YyVnFoLzNnbCtaQWxYd003M3FnY2c0NCtKbGlra1YwVVE9PSIsIm1hYyI6IjVjNjdkYzRlYTk4ZWYzZTJmMzIwNDAxODBkNTAzN2RkNjczNzhmOTJmNDM2MmQ0OGJkZmM0MTE2ZjY0NGQyNWYiLCJ0YWciOiIifQ%3D%3D
DNT
	1
Host
	localhost:8000
Origin
	http://localhost:8000
Pragma
	no-cache
Referer
	http://localhost:8000/login
Sec-Fetch-Dest
	empty
Sec-Fetch-Mode
	cors
Sec-Fetch-Site
	same-origin
User-Agent
	Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0
X-CSRF-Token
	r380iy2Dd4K26pDGoxzZrchMSx5YaNoZSNzEhQaR
X-Requested-With
	XMLHttpRequest
X-XSRF-TOKEN
	eyJpdiI6ImNXTzh3TE95c0YvMkJJMTU0TTZ1VXc9PSIsInZhbHVlIjoiYVJUclpjbmFQZjZzQjYxNUtUZTBQamtBOHFiRzczSXN5cG1kMmlXSk4vZnR4MDRwSDdJWjd2L29teURRTm1CU1JGOElScGZCMmNReGJHOFhtcXNCZ2VmRXhyRnhKajZEK3ozSzdTdW96Y1lPYmo0NGVRL0FlNENrU1FPRHRtSTciLCJtYWMiOiI2NDY1MTIxNzE1ZDdjMjEyY2E5YzQ4ODYzYjk2ZmE1MWViNGJmYzY2OTZlY2MxODgzZWRlMDZiMzNjYzk0MzIwIiwidGFnIjoiIn0=

One thing i noticed is the XSRF-TOKEN cookie httpOnly is set to false while the other two cookies are set to true. Not sure if this is relevant.

0 likes
1 reply
faust's avatar
faust
OP
Best Answer
Level 1

I solved my issue by removing the following line from bootstrap.js

axios.defaults.headers.common['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content')

Please or to participate in this conversation.