Chimezie Enyinnaya I'm a self-taught software developer based in Lagos, Nigeria. I enjoy teaching what I have learned and what I'm currently learning so that others can benefit from it.

Laravel Sanctum tutorial: Authenticating Nuxt.js SPAs

7 min read 2127

laravel-sanctum-tutorial-authenticating-nuxtjs-spas

Dealing with authentication in single-page applications (SPA) can be tricky. Often, developers simply use local storage to save users tokens. However, local storage isn’t very secure, so it’s generally recommended to use something that offers more protection, such as cookies.

In this tutorial, we’ll show you how to implement authentication in a Nuxt.js SPA using Laravel Sanctum. To demonstrate how this works, we’ll walk through the process of building a simple Nuxt.js app with authentication powered by a Laravel API.

We’ll cover the following in detail and with examples:

To follow along with this demonstration, you should have basic knowledge of Laravel and Nuxt.js.

What is Laravel Sanctum?

Laravel Sanctum is a Laravel package for authentication of SPAs, mobile applications, and basic, token-based APIs. Depending on what you’re building, Laravel Sanctum can be used to generate API tokens for users or authenticate users with a Laravel session.

Creating a Laravel app

Let’s start our demo by creating a new Laravel application.

To create a new Laravel app, use the Laravel Installer:

laravel new laravel-sanctum-nuxtjs-api

Once that’s done, run the following command to start the application:

cd laravel-sanctum-nuxtjs-api
php artisan serve

The application should now be running on http://127.0.0.1:8000. We’re going to leave it running for the rest of the tutorial.

Setting up Laravel Sanctum

To set up Sanctum, first install it:

We made a custom demo for .
No really. Click here to check it out.

composer require laravel/sanctum

Once it’s installed, you can publish Sanctum vendor files:

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

This creates a sanctum.php file inside the config directory, which is used to configure Sanctum. It will also create a migration file for a personal_access_tokens table, which is used to store access tokens.

Before we run the migrations, let’s set up the database for our application. To keep things simple, we’ll use SQLite.

Create a database.sqlite file:

touch database/database.sqlite

Update the .env file to reflect this:

// .env

DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/database.sqlite

Now, we run the database migrations:

php artisan migrate

For Sanctum to generate access tokens for users, the User model need to use the HasApiTokens trait:

// app/Models/User.php

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
  use HasApiTokens, HasFactory, Notifiable;
}

One of the benefits of using Sanctum is that it uses the normal Laravel’s session cookies for authentication in an SPA.

To configure the domains from which our SPA will make request, go into the sanctum.php file and update the stateful key accordingly:

// config/sanctum.php

'stateful' => explode(',', env(
  'SANCTUM_STATEFUL_DOMAINS',
  'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1'
)),

Instead of updating the file directly, we’ll use the environment variables:

// .env

SESSION_DRIVER=cookie
SANCTUM_STATEFUL_DOMAINS=localhost:3000
SESSION_DOMAIN=.localhost

Typically, the domains should include your local and production domains, which access your API via a SPA. I set it to just localhost:3000 because that’s where the SPA will be running. In addition to the stateful domains, we also set the session driver and domain.

Next, we need to register Sanctum’s middleware in the api middleware group inside the app/Http/Kernel.php file:

// app/Http/Kernel.php

use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
  EnsureFrontendRequestsAreStateful::class,
  ...
]

This middleware will ensure that incoming requests from our SPA can authenticate using Laravel’s session cookies.

Lastly, let’s ensure that our application’s CORS configuration is returning the Access-Control-Allow-Credentials header with a value of True. We can do that by updating the cors.php as follows:

// config/cors.php

'supports_credentials' => true

Building a Laravel API

With all the set up out of the way, let’s start building our Laravel API. To keep things simple, the API will only contain endpoints for authenticating users, fetching authenticated users, and logging out users.

Of course, users needs to be registered before they can perform authentication. So let’s seed the database with a dummy user that we can use to test the authentication system. Do that directly inside DatabaseSeeder.php:

// database/seeders/DatabaseSeeder.php

use App\Models\User;

User::create([
  'name' => 'John Doe',
  'email' => 'johndoe@example.com',
  'password' => bcrypt('password'),
]);

Next, run the seeder:

php artisan db:seed

Create the /login endpoint inside routes/web.php:

// routes/web.php

Route::post('login', 'AuthController@login');

Then, create the AuthController:

php artisan make:controller AuthController

Let’s add the login method:

// app/Http/Controllers/AuthController.php

use Illuminate\Support\Facades\Auth;

public function login(Request $request)
{
  if (Auth::attempt($request->only('email', 'password'))) {
    $request->session()->regenerate();
  }

  return response()->json([
    'message' => 'Invalid login details'
  ], 401);
}

Here, we attempt to authenticate the user with the supplied details. If no match is found, we simply return an appropriate JSON response. Otherwise, a session is started for the user.

Inside routes/web.php, create the /logout endpoint:

// routes/web.php

Route::post('logout', 'AuthController@logout');

To add the logout functionality:

// app/Http/Controllers/AuthController.php

public function logout(Request $request)
{
  Auth::logout();

  $request->session()->invalidate();

  $request->session()->regenerateToken();
}

logout() removes the user’s details from the session. Then, we invalidate the user’s session and, lastly, regenerate the CSRF token.

Since we’re going to be making requests to these routes from a different domain — hat is, from the SPA — let’s make sure cross-origin requests are allowed to /login and /logout by adding them to the paths array inside config/cors.php:

// config/cors.php

'paths' => [
  ...,
  'login',
  'logout',
],

To add the implementation for fetching an authenticated user, create the /api/user endpoint inside routes/api.php:

// routes/api.php

Route::get('user', 'AuthController@me');

Next, add the me method:

// app/Http/Controllers/AuthController.php

public function me(Request $request)
{
  return response()->json([
    'data' => $request->user(),
  ]);
}

Here, we simply return a JSON response containing the currently authenticated user.

As you might have guessed, the /auth/user endpoint will be accessible to only authenticated users. So let’s make sure of that by making use of the sanctum authenticated guard.

Update the route as follows:

// routes/api.php

Route::get('/auth/user', 'AuthController@me')->middleware('auth:sanctum');

This will ensure requests to the endpoint contain an authorization header with a valid token.

Lastly, let’s uncomment the line below inside RouteServiceProvider.php:

// app/Providers/RouteServiceProvider.php

protected $namespace = 'App\\Http\\Controllers';

Creating a Nuxt.js application

Now, let’s move on to the SPA itself. We’ll start by creating a new Nuxt.js application.

To create a Nuxt.js application, simply use the command below:

npx create-nuxt-app laravel-sanctum-nuxtjs-app

When prompted, select the options that makes sense to you. Here’s what I selected:

Nuxt prompt selections

Once everything is done installing, start the application:

cd laravel-sanctum-nuxtjs-app
npm run dev

For authentication, we’ll use the nuxt/auth module.

To install the nuxt/auth module:

npm install --save-exact @nuxtjs/auth-next

Next, add @nuxtjs/auth-next to the modules array of nuxt.config.js:

// nuxt.config.js

{
  modules: [
    ...,
    '@nuxtjs/auth-next',
  ]
}

Finally, update axios object as shown below:

// nuxt.config.js

axios: {
  credentials: true,
},

Creating a login page

To style our login page, we’ll use Bulma CSS, which we installed when creating the Nuxt.js application.

Let’s create the login page. Inside the pages directory, create a login.vue file and add the following code:

// pages/login.vue

<template>
  <section class="section">
    <div class="container">
      <div class="columns is-centered">
        <div class="column is-one-third">
          <h2 class="title has-text-centered">Login</h2>

          <form method="post" @submit.prevent="login">
            <div class="field">
              <label class="label">Email</label>
              <div class="control">
                <input type="email" class="input" v-model="email" required />
              </div>
            </div>

            <div class="field">
              <label class="label">Password</label>
              <div class="control">
                <input
                  type="password"
                  class="input"
                  v-model="password"
                  required
                />
              </div>
            </div>

            <div class="control">
              <button type="submit" class="button is-dark is-fullwidth">
                Login
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
  </section>
</template>

Here, we have a basic login form, which, when submitted, calls a login method:

Basic Login Form Visual

Before we create the login method, let’s configure nuxt-auth to make use of Laravel Sanctum. We can do that by adding the snippet below inside nuxt.config.js:

// nuxt.config.js

auth: {
  strategies: {
    laravelSanctum: {
      provider: 'laravel/sanctum',
      url: 'http://localhost:8000',
    },
  },
},

Note: The domain set as the url has to be the same as the SPA. Since the SPA is running on http://localhost:3000, the url is set to http://localhost:8000.

We set the Laravel Sanctum provider as the strategy the nuxt-auth module will use for authentication. Under the hood, the Laravel Sanctum provider makes requests to:

  • /sanctum/csrf-cookie, which issues a XSRF-TOKEN cookie as a header
  • /login, the endpoint we created inside routes/web.php, when logging in
  • The /api/user route in our Laravel application when fetching the authenticated user.

Now, we can add the functionality for the login method inside login.vue:

// pages/login.vue

<script>
export default {
  data() {
    return {
      email: '',
      password: '',
    }
  },
  methods: {
    async login() {
      await this.$auth.loginWith('laravelSanctum', {
        data: {
          email: this.email,
          password: this.password,
        },
      })

     this.$router.push('/')
    },
  },
}
</script>

First, we define some data properties. Then we have the login method, where we are logging using the Laravel Sanctum provider.

Under the hood, the provider first makes a request to /sanctum/csrf-cookie to grab a CSRF token and set it as a XSRF-TOKEN cookie, which is used in subsequent requests. Then, it makes a POST request to the /l``ogin endpoint. Upon successful login, the user is redirected to the homepage.

Updating the homepage

For now, the homepage contains the default content from when we created the Nuxt.js app. Let’s update it to display the authenticated user’s name and a way to log out.

Replace the content of pages/index.vue with the following:

// pages/index.vue

<template>
  <section class="section">
    <div class="container">
      <h1 class="title">Dashboard</h1>

      <p>Hi {{ user.name }}</p>

      <a href="#" @click.prevent="logout">Logout</a>
    </div>
  </section>
</template>

<script>
export default {
  data() {
    return {
      user: this.$auth.user.data,
    }
  },
  methods: {
    async logout() {
      await this.$auth.logout()

      this.$router.push('/login')
    },
  },
}
</script>

Under the hood, the Laravel Sanctum provider makes a request to the /api/user endpoint to fetch the authenticated user. We can get the user’s details through this.$auth.user, which we simply assign to a user data property.

To log out, we simply call the logout method, then redirect to the login page.

Upon a successful login, we should get something like this:

Return of Successful Login

Restricting access

The homepage is serving as the profile page, so let’s make sure only authenticated users can access it. We can do that by making use of the auth middleware provided by the nuxt-auth.

Add the following code inside nuxt.config.js:

// nuxt.config.js

router: {
  middleware: ['auth']
},
>

Now, when authenticated users try to access the homepage, they’ll be redirected to the login page.

Conclusion

In this tutorial, we showed you how to use Laravel Sanctum to implement authentication in a Nuxt.js SPA.

To learn more about Laravel Sanctum, check out the Laravel Sanctum docs.

You can get the complete source code for our demo API and SPA on GitHub.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Chimezie Enyinnaya I'm a self-taught software developer based in Lagos, Nigeria. I enjoy teaching what I have learned and what I'm currently learning so that others can benefit from it.

Leave a Reply