I am working on a project with a React front-end and a Laravel back-end. I am trying to set up my authentication system. I am utilizing SPA authentication using Sanctum. I am successfully utilizing the sanctum/csrf-cookie route, where the XSRF-Token cookie is given. When I then try to follow that up with a login, I get a 419 error, CSRF token mismatch. There is no XSRF-Token. What is interesting is that if I do a get request, as in the ‘testing’ route below, the XSRF cookie is present. However, when I do a post request, as in posting to the login route, the cookie is not present and I get a 419 error.
I am running this locally right now. The front-end is running at localhost:3000, with the back-end running at localhost:8888. Here are various relevant segments of code.
LoginForm.js
let data = {
email: e.target[0].value,
password: e.target[1].value
}
axios.get('http://localhost:8888/sanctum/csrf-cookie')
.then((res) => {
axios.post('http://localhost:8888/login', data)
.then((res) => {
axios.get('http://localhost:8888/user')
})
})
Kernel.php
protected $middleware = [
AppHttpMiddlewareTrustProxies::class,
FruitcakeCorsHandleCors::class,
AppHttpMiddlewarePreventRequestsDuringMaintenance::class,
IlluminateFoundationHttpMiddlewareValidatePostSize::class,
AppHttpMiddlewareTrimStrings::class,
IlluminateFoundationHttpMiddlewareConvertEmptyStringsToNull::class,
IlluminateSessionMiddlewareStartSession::class,
IlluminateSessionMiddlewareAuthenticateSession::class,
IlluminateViewMiddlewareShareErrorsFromSession::class,
];
protected $middlewareGroups = [
'web' => [
AppHttpMiddlewareEncryptCookies::class,
IlluminateCookieMiddlewareAddQueuedCookiesToResponse::class,
IlluminateViewMiddlewareShareErrorsFromSession::class,
AppHttpMiddlewareVerifyCsrfToken::class,
IlluminateRoutingMiddlewareSubstituteBindings::class,
AppHttpMiddlewareHandleInertiaRequests::class,
],
'api' => [
LaravelSanctumHttpMiddlewareEnsureFrontendRequestsAreStateful::class,
'throttle:api',
IlluminateRoutingMiddlewareSubstituteBindings::class,
],
];
protected $routeMiddleware = [
'auth' => AppHttpMiddlewareAuthenticate::class,
'auth.basic' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class,
'cache.headers' => IlluminateHttpMiddlewareSetCacheHeaders::class,
'can' => IlluminateAuthMiddlewareAuthorize::class,
'guest' => AppHttpMiddlewareRedirectIfAuthenticated::class,
'password.confirm' => IlluminateAuthMiddlewareRequirePassword::class,
'signed' => IlluminateRoutingMiddlewareValidateSignature::class,
'throttle' => IlluminateRoutingMiddlewareThrottleRequests::class,
'verified' => IlluminateAuthMiddlewareEnsureEmailIsVerified::class,
];
.env
SESSION_DRIVER=cookie
CLIENT_URL=http://localhost:3000
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=http://localhost:3000
Bootstrap.js
axios = require('axios');
axios.defaults.headers.common['Accept'] = 'application/json';
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.withCredentials = true;
Web.php
Route::get('/testing', function () {
return "Testing.";
});
Route::post('/login', function(Request $request) {
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
$id = Auth::id();
$user = User::find($id);
return $user;
}
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
]);
});
Sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,localhost:8888,
Sanctum::currentApplicationUrlWithPort()
))),
Cors.php
'paths' => [
'api/*',
'sanctum/csrf-cookie',
'login',
'logout',
'register',
'user/password',
'forgot-password',
'reset-password',
'user/profile-information',
'email/verification-notification',
'testing',
'user',
'checkAuth'
],
'allowed_methods' => ['*'],
'allowed_origins' => [env('CLIENT_URL')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
2
Answers
Let’s review one by one what is needed to apply SPA authentication using sanctum.
supports_credentials
to betrue
onconfig/cors.php
. Next, you should enable the withCredentials option on your application’s global axios instance.axios.defaults.withCredentials = true;
SESSION_DOMAIN
, looks like you have already set localhost here.Overall, it seems you need to fix to things:
supports_credentials
to true onconfig/cors.php
For more detail, you can follow: https://laravel.com/docs/9.x/sanctum#spa-authentication
As per the Laravel Sanctum documentation :
Since we cannot find misconfigurations in your setup, I will recommend adding a custom middleware in your
api
group that automatically adds theapplication/json
header :And add it to your ‘api’ middlewareGroups :