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')
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
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.
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.