Constantin Druccdruc

Hey, I'm Constantin Druc!

I'm a web developer sharing everything I know about building web applications.

Fastest way to get Laravel SPA authentication up and running

The goal of this post is not to explain what/how/why things work but to skip all the fluff and get your Laravel SPA authentication up and running in no time.

I plan to publish a more in-depth post and a video on this topic, so be sure to keep an eye on this blog, subscribe to my YT channel, and follow me on twitter.

Let's goooowww.

Laravel quick setup

  1. Create a new Laravel application
laravel new api
  1. cd into your new application and install laravel/breeze
cd api
composer require laravel/breeze
  1. Scaffold authentication API using Laravel Breeze
php artisan breeze:install api
  1. Open .env and:
  • set FRONTEND_URL to the url of your SPA. This is usually http://localhost:5173 for a VueJS app using Vite.
  • set SANCTUM_STATEFUL_DOMAINS to the url of your SPA but without the http protocol.
  • update your database connection details.
FRONTEND_URL=http://localhost:5173
SANCTUM_STATEFUL_DOMAINS=localhost:5173

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=api
DB_USERNAME=root
DB_PASSWORD=yourRootPassword

Make sure the frontend url and stateful domain don't contain a trailing slash /.

In our example, it's http://localhost:5173 not http://localhost:5173/, and localhost:5173 not localhost:5173/.

  1. Open database/DatabaseSeeder.php and seed a test user:
\App\Models\User::factory()->create([
    'name' => 'Test User',
    'email' => 'test@example.com',
]);
  1. Start your Laravel application.
php artisan serve

Usually, Laravel will run at http://127.0.0.1:8000, but you can also access it at http://localhost:8000.

Because the Laravel API and Frontend App must run on the same top-level domain, we'll use the second url (localhost) when performing our frontend HTTP requests.

We are done configuring Laravel!

Frontend instructions

We'll use the axios library to perform HTTP requests. Axios is a more user-friendly alternative and does some good things out of the box (like automatically sending the csrf cookie as X-XSRF-TOKEN header).

Make sure you configure axios to include credentials (cookies) with every request. Then, before you attempt to authenticate or register an account, request a csrf token by making an initial GET request to http://localhost:8000/sanctum/csrf-cookie.

// configure axios to include credentials
axios.defaults.withCredentials = true;

// ask for csrf cookie
axios.get('http://localhost:8000/sanctum/csrf-cookie')
  .then(() => {
    // attempt to authenticate
    axios.post('http://localhost:8000/login', {
      email: form.value.email,
      password: form.value.password,
    }).then(() => {
       // user is authenticated 
    });
  });

Once the authentication is successful, every subsequent request you make will be treated as an "authenticated request". For example, you can make a request to get the current user details (the endpoint is included by default in routes/api.php).

axios.get(`http://localhost:8000/api/user`)
  .then((response) => {
     console.log(response.data); // outputs user details 
  });

However, the session will expire at some point due to user inactivity. When that happens, you will get back a 419 or 401 response code, in which case you need to redirect the user to the login page.

When using axios, you could set up a response interceptor like the one bellow:

axios.interceptors.response.use(function (response) {
    return response;
}, function (error) {
    if (error.request.status === 403) {
        // redirect to a 403 page informing
        // the action is forbidden
        router.push('/403');
    }
    
    if ([401, 419].includes(error.request.status)) {
        // redirect to login
        router.push('/login');
    }
    return Promise.reject(error);
});

And that was it. Your Laravel SPA authentication should be up and running. If it's not, and things didn't work out as expected, subscribe and wait for the in-depth video or hit me up on twitter. Bye!