I am building an SPA using React 18 connected to a Laravel 10 API, which already includes Sanctum for authentication and I installed Breeze API to generate the respective scaffolding.
I have followed the official Laravel documentation but, because it was not enough to solve this issue, I started to research with google what other developers have done do get rid of this inconvenient.
I have read about many different fixes and tried them almost all (some of them do not make sense at all or are not recommended) until I came to an old post, in github, answered by Taylor Otwell himself when Sanctum used to be called Airlock:
So, I created a domain and a subdomain in WAMP, to test this possible solution, but it is frustrating to still see the same error popping up:
As you all can see, the GET requests do not have problem but the POST requests.
For this project I installed AXIOS, but I noticed the cookies are not being sent to the API server when posting data.
axios.js
import axios from "axios";
export default axios.create({
baseURL: 'http://api.localdomain',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
withCredentials: true
})
This is my .env file:
APP_NAME=localdomain
APP_ENV=local
APP_KEY=base64:SjhdnO/kZ2IMWGKEdgrw+a5e1eToxvtLHsbJvwf0dAs=
APP_DEBUG=true
APP_URL=http://api.localdomain
FRONTEND_URL=http://localdomain
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
SESSION_DOMAIN=.localdomain
SANCTUM_STATEFUL_DOMAINS=api.localdomain
config/cors.php
'paths' => [
'api/*',
'login',
'logout',
'register',
'user/password',
'forgot-password',
'reset-password',
'sanctum/csrf-cookie',
'user/profile-information',
'email/verification-notification',
],
'allowed_methods' => ['*'],
'allowed_origins' => [env('FRONTEND_URL')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
config/session.php
'domain' => env('SESSION_DOMAIN'),
'http_only' => true,
'same_site' => 'lax',
config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf('%s%s%s',
'api.localdomain,localdomain,127.0.0.1,127.0.0.1:8000,::1',
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' => AppHttpMiddlewareVerifyCsrfToken::class,
'encrypt_cookies' => AppHttpMiddlewareEncryptCookies::class,
],
routes/auth.php
<?php
use AppHttpControllersAuthAuthenticatedSessionController;
use AppHttpControllersAuthNewPasswordController;
use AppHttpControllersAuthPasswordResetLinkController;
use AppHttpControllersAuthRegisteredUserController;
use IlluminateSupportFacadesRoute;
Route::post('/register', [RegisteredUserController::class, 'store'])->middleware('guest');
Route::post('/login', [AuthenticatedSessionController::class, 'store'])->middleware('guest');
Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])->middleware('auth');
Route::post('/forgot-password', [PasswordResetLinkController::class, 'store'])->middleware('guest');
Route::post('/reset-password', [NewPasswordController::class, 'store'])->middleware('guest');
Those routes are the ones generated with:
# Install Breeze and dependencies...
composer require laravel/breeze --dev
php artisan breeze:install api
I have tried many different combinations of possible values in the .env file, cors.php and sanctum.php, but so far with no positive results.
Any help on how to make this work is really appreciated, as I have spent 3 days trying different fixes to no avail.
EDIT: (2023/08/19)
Taking a look at how Axios determines whether to set the response header ‘X-XSRF-TOKEN’ or not, I came to know that the POST request (/login route) header value comes from the document.cookies
string. However, the document.cookies is not set by Laravel (or one of its packages), despite of the fact that I changed:
'domain' => '.localdomain',
'secure' => false,
'http_only' => false,
'same_site' => 'lax'
values in config/session.php
I have tested this in my dev environment and in my local domain. The result is the same (status code 419 / CSRF token mismatch).
There is still something missing or is misconfigured in the backend side.
2
Answers
Ok, after studying and testing a lot, I finally came to the answer that many may be looking for: using a ReactJS SPA authenticated by Sanctum (Laravel 10) without much changes in the Laravel configuration.
As a matter of fact, when you make a fresh installation of Laravel, it already includes the Sanctum package, so what you need to do is to install Breeze in order to create the routes and authentication scaffolding. The whole installation process is as follows:
That's it. You don't need to change anything in the configuration files nor in the original routes, unless you know exactly what you want to do.
The only required changes will be in the
.env
file of course, and perhaps thepath
variable inconfig/cors.php
file. Besides that, nothing else needs to be done.As for my case, this is how I configured my
.env
file:Reading the comments inside the
config/session.php
file, I realized thatHTTPS
protocol should be used in order to send the session cookies back to the server. Of course!, if we are going to send a cookie holding sensitive data for authentication, then it makes sense to useHTTPS
protocol. If not, the session cookie is not sent to the server and that's why we get the 419 error.For this to work properly, I used the Mkcert software to create my certificates. How to create locally trusted SSL certificates
That software saved me many headaches.
Once the virtual hosts were created with their respective certificates, my SPA ran just fine. Again, Taylor Otwell was right when he said that for this to work, a real domain and subdomain are necessary.
I hope this can help someone else, because there are many strange fixes and myths out there that really have few or nothing to do with running a SPA with Laravel Sanctum properly.
Edit
Fetch the CSRF token and set it in the cookies
When making the POST request for login, ensure that the X-XSRF-TOKEN header is set with the CSRF token from the cookies. This header is automatically included by Axios when using the CSRF token cookie.
Update:
You also need to set withCredentials in resources/js/bootstrap.js