skip to Main Content

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:

enter image description here

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:

enter image description here

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


  1. Chosen as BEST ANSWER

    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:

    1. composer create-project --prefer-dist laravel/laravel my-api
    2. cd my-api
    3. composer require laravel/breeze --dev
    4. php artisan breeze:install api

    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 the path variable in config/cors.php file. Besides that, nothing else needs to be done.

    As for my case, this is how I configured my .env file:

    APP_NAME=localdomain
    APP_ENV=local
    APP_KEY=base64:bla bla bla=
    APP_DEBUG=true
    APP_URL=https://api.localdomain.dom
    FRONTEND_URL=https://localdomain.dom
    
    SESSION_DOMAIN=.localdomain.dom
    SESSION_DRIVER=cookie
    SESSION_LIFETIME=120
    

    Reading the comments inside the config/session.php file, I realized that HTTPS 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 use HTTPS 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.


  2. Edit
    Fetch the CSRF token and set it in the cookies

    axios.get('/sanctum/csrf-cookie').then(response => {
        // Make other requests here and set cookie
    });
    

    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

    axios.defaults.withCredentials = true;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search