Authentication in Nuxt.js using Laravel Sanctum

Authentication in Nuxt.js using Laravel Sanctum

You should never save authorization tokens in local storage or cookies, as they can be accessed by any third-party JavaScript code running in the user's browser. By simply using a code like this localStorage.getItem('auth_token'), tokens can be stolen and misused. By saving tokens in local storage or insecure cookies, you are jeopardizing the security of your user's account.

Laravel Sanctum is a package made by Taylor Otwell which solves this issue by using a special kind of cookies called HttpOnly cookies. These cookies are set by the server, and cannot be read by the JavaScript code running on the client-side aka browser. When these cookies are set by the server, the browser automatically sends these cookies back to the server with every request, and thus the server knows it an authorized request.

Laravel Sanctum also takes care of CSRF protection by including CSRF cookie in each request. And also protects your site against XSS based attacks.

Introduction

In this tutorial, we will build a sample Nuxt.js application which will demo the authentication flow using Laravel sanctum. We are going to build our Nuxt application in the SPA (Single page application) mode. If you want to deploy your application in universal (SSR - Server-side rendered) mode, you can still do it.

Authentication in the Nuxt using Laravel sanctum does work in SSR mode. But it doesn't make much sense if your application running SSR mode if the application requires login to access and search engine can access your site without a login.

SPA and Backend domains

To work with Sanctum, we should be familiar with a few things first. We must use our SPA and API backend on the same domain, like frontend on domain.com and API on api.domain.com. We cannot set frontend on domain.com and backend (API) on another-domain.com. The client must be able to include cookies with each request being sent to the backend.

Please check out Mozilla developer documentation on withCredentials & article by Mohamed Said on Authentication and Laravel Sanctum for information on session using cookies.

Here is my little advice, when you are developing the Nuxt site in local, use php artisan serve command to serve Laravel backend at http://localhost:8000 instead of using valet like sanctum-nuxt.test.

Setting up Laravel Sanctum

Enough of theory! Let's begin by setting up Laravel sanctum for your Laravel application. Just follow these steps carefully to configure your app.

In your Laravel 7 app, install the sanctum package using composer:

composer require laravel/sanctum

Next, publish sanctum configuration & database migration files.

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

Then, we will need to run our migration to create personal_access_tokens table, which will be used by Sanctum to save access tokens for the users.

php artisan migrate

Since we are using Sanctum for our SPA, we need to make sure that our HTTP request pass through Sanctum middleware. Configure api middleware group in app/Http/Kernel.php to use Sanctum middleware.

// FILE: app/Http/Kernel.php

use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

protected $middlewareGroups = [
  ...
  'api' => [
      EnsureFrontendRequestsAreStateful::class, // Add & import this class
      'throttle:60,1',
      \Illuminate\Routing\Middleware\SubstituteBindings::class,
  ],
];

Next, let's configure domains for our SPA. To make sure our SPA works with Sanctum, set the appropriate values for SESSION_DOMAIN and SANCTUM_STATEFUL_DOMAINS inside .env file at the root of our application.

SESSION_DOMAIN="localhost"
SANCTUM_STATEFUL_DOMAINS="localhost"

Also, make sure our SPA domain is configured and have set supports_credentials to true in CORS config/cors.php.

// FILE: config/cors.php

return [
    ...
    'paths' => ['*'],
    'allowed_origins' => ['http://localhost:3000'],
    'supports_credentials' => true,
    ...
];

And our last step should be to use Sanctum auth middleware auth:sanctum to protect our routes like this in routes/api.php.

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

And we are done! Now that we have finished setting up our API backend with Sanctum. Let's proceed for setting up our Nuxt SPA app to use our API.

Nuxt application setup

Let's begin by setting up the Nuxt.js app first, and then the Laravel-based API backend using Sanctum. Before that, let me provide you a little information on how to set up your domains to work with the Sanctum's SPA authentication.

Create a new Nuxt.js project by entering the following command in your terminal.

npx create-nuxt-app your-project-name

Next, setup will ask you a series of questions, answers them as your preferences.

? Project name: sanctum-nuxt
? Project description: My fantastic Nuxt.js project
? Author name: Swapnil Bhavsar
? Choose the package manager: Npm
? Choose UI framework: Tailwind CSS
? Choose custom server framework: None (Recommended)
...

When setup asks for choosing Nuxt.js modules, be sure toggle axios the option, We will need it, so that our Nuxt app can make HTTP requests.

? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection)
- Axios

In the final step, choose the Single Page App when setup asks for, choose rendering mode. Of course, you can also choose to render your app in the universal mode. But for the scope of this tutorial, we will deploy our app in the spa mode.

? Choose rendering mode (Use arrow keys)
- Single Page App

Auth module

The Auth module is an official package provided by the Nuxt.js community which adds authentication support for the Nuxt.js app. It provides helper objects like $auth, which can be used to log in a user or access an authenticated user.

To allow our app to authenticate with our API backend, we will need to create an auth strategy scheme in the auth section of nuxt.config.js. But before that, we need to install the Auth Module.

Install the Auth module by running the following command in terminal:

npm install @nuxtjs/auth

Then, register the auth module in nuxt.config.js like this:

modules: [
  '@nuxtjs/axios',
  '@nuxtjs/auth'
],
auth: {
  // Options
}

Before proceeding to configuring the auth module, set baseURL and credentials options for the axios section in nuxt.config.js. Setting credentials: true will include cookies in the HTTP request made to the server.

axios: {
  baseURL: "http://localhost:8000",
  credentials: true
},

Next, configure the auth module in nuxt.config.js to authenticate with our Laravel application endpoints like this:

auth: {
  redirect: {
    login: '/login',
    logout: '/',
    callback: '/login',
    home: '/'
  },
  strategies: {
    local: {
      endpoints: {
        login: { url: '/login', method: 'post', propertyName: false },
        user: { url: '/api/user', method: 'get', propertyName: false }
      },
      tokenRequired: false,
      tokenType: false
    }
  },
  localStorage: false
},

After adding the local strategy in the auth section, let's proceed to creating a login page for our Nuxt app.

Creating a login page

Create a file called login.vue under the pages directory in your Nuxt.js project with the following content. Before loading, it sends a request to /sanctum/csrf-cookie path so that the server could initialize CSRF protection for the application. It also has a login form which uses $auth.loginWith a function provided by the auth module to submit the login form.

<template>
  <div class="flex h-screen items-center justify-center">
    <form ref="loginform" @submit.prevent="login()" class="w-1/4 mx-auto p-4">
      <h1 class="font-semibold mb-2 text-xl">
        Login
      </h1>
      <div class="mb-4">
        <label for="email" class="block mb-1 text-sm">Email</label>
        <input
          type="email"
          name="email"
          class="w-full border rounded px-3 py-2"
          required
        />
      </div>
      <div class="mb-4">
        <label for="password" class="block mb-1 text-sm">Password</label>
        <input
          type="password"
          name="password"
          class="w-full border rounded px-3 py-2"
          required
        />
      </div>
      <button
        type="submit"
        class="bg-blue-500 text-white font-semibold py-2 px-10 w-full rounded"
      >
        Login
      </button>
    </form>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        error: {},
      };
    },
    mounted() {
      // Before loading login page, obtain csrf cookie from the server.
      this.$axios.$get('/sanctum/csrf-cookie');
    },
    methods: {
      async login() {
        this.error = {};
        try {
          // Prepare form data
          const formData = new FormData(this.$refs.loginform);

          // Pass form data to `loginWith` function
          await this.$auth.loginWith('local', { data: formData });

          // Redirect user after login
          this.$router.push({
            path: '/',
          });
        } catch (err) {
          this.error = err;
          // do something with error
        }
      },
    },
  };
</script>

Access authenticated user

You can access the authenticated user data by using this.$auth.user. For example, create an account.vue the page which will show the user's information like this in the /page directory.

<template>
  <div>
    <p>Name: {{ $auth.user.name }}</p>
    <p>Name: {{ $auth.user.email }}</p>
  </div>
</template>

<script>
  export default {
    middleware: 'auth',
  };
</script>

You can use auth middleware to make sure that your pages are only accessible by authenticated users. You can find more information on using middleware here: https://auth.nuxtjs.org/guide/middleware.html

Conclusion

This is a bare minimum example for you to get started with authentication in Nuxt.js using Laravel Sanctum. I am pretty amazed by the simplicity provided by the Sanctum package over Laravel Passport when implementing API authentication for your applications.

This was my first article on my blog, hope you will find it useful. You can ask me questions or send feedback about the article on Twitter @swapnil_bhavsar.