SPA Authentication using Laravel 11 Sanctum Vue 3 and Vite

SPA Authentication using Laravel 11 Sanctum vue 3 and vite tutorial, will show you how to implement a robust authentication system in Laravel 11 and Vue.js 3 applications using the Laravel Sanctum package and Vite.

This tutorial guides you through building a RESTful Authentication API, equipping your Laravel 11 backend and Vue.js 3 frontend with essential functionalities such as user registration, login, and logout.

Laravel Vue Authentication using Sanctum and Vite SPA Example

Follow the following steps to implement SPA Authentication using Laravel 11 Sanctum and Vue 3.

  • Install Laravel 11 App
  • Configure Database Details
  • Run Migrate Command
  • Create Controller
  • Configure Routes
  • Install npm Pacakges
  • Initiate Vue in Laravel
  • Create Vue Js Auth Components
  • Create Vue Routes for Laravel Vue Authentication
  • Include Dependencies to app.js
  • Send Authorization token in Header for Every requests
  • Run Development Server

#1 Install Laravel 11 App

Before proceeding with the installation of Laravel 11, ensure that your system meets the following prerequisites:

  • Laravel 11 requires PHP version 8.2 or higher. You can verify your PHP version by running php -v in your terminal or command prompt. If you don’t have PHP 8.2 installed, you’ll need to upgrade your PHP version.
  • Laravel supports multiple database systems such as MySQL, PostgreSQL, SQLite, and SQL Server. In Laravel 11, they have predefined DB_CONNECTION=sqlite in the .env file, but if you are using MySQL, you will need to update it.

To install Laravel 11, run the following command in your terminal:

composer create-project laravel/laravel:^11.0 laravel-vuejs-authentication

If you prefer to install Laravel based on your PHP version, use the following command:

composer create-project laravel/laravel laravel-vuejs-authentication

Once the installation is complete, navigate to the project directory:

cd laravel-vuejs-authentication

#2 Connect Database to App

After that, add your database credentials to the .env file, such as the database name, username, and password. The .env file is located in your project’s root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=#db_name
DB_USERNAME=#db_username
DB_PASSWORD=#db_password

#3 Run Migrate Command

In Laravel’s latest version (from Laravel 8+), the Sanctum package is readily available, eliminating the need for separate installation. Simply execute the migrate command to generate predefined migration files, setting up tables necessary for Laravel Vue.js authentication with Sanctum.

php artisan migrate

Note: If you are running below the Laravel 8 version then you need to install Senctum through the Composer package manager.

#4 How to Create Controller

Now you need to create a controller to handle the Laravel vue.js API authentication system. Run the below command and it will generate a controller file in your application.

php artisan make:controller Api/AuthController

Now, open the app\Http\Controllers\Api\AuthController.php file and put the below code on it.

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use App\Models\User;

class AuthController extends Controller
{
    public function register(Request $request)
    {

        $data = Validator::make($request->all(),[
            'name' => 'required',
            'email' => 'required|string|email|unique:users',
            'password' => 'required|string|min:8',
        ]);

        if ($data->fails()) {
            return response()->json([
                'errors' => $data->errors()
            ], 422);
        }

        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);

        $token = $user->createToken('auth_token')->plainTextToken;

        return response()->json([
            'access_token' => $token,
            'token_type' => 'Bearer',
        ], 200);
    }

    public function login(Request $request)
    {
        $data = Validator::make($request->all(),[
            'email' => 'required',
            'password' => 'required',
        ]);

        if ($data->fails()) {
            return response()->json([
                'errors' => $data->errors()
            ], 422);
        }

        if (!Auth::attempt($request->only('email', 'password'))) {
            return response()->json([
                'message' => 'Invalid login details'
            ], 401);
        }

        $user = User::where('email', $request['email'])->firstOrFail();
        $token = $user->createToken('auth_token')->plainTextToken;

        return response()->json([
            'user' => $user,
            'access_token' => $token,
            'token_type' => 'Bearer',
        ], 200);
    }

    public function user(Request $request)
    {
        return $request->user();
    }

    public function logout (Request $request)
    {
        $request->user()->tokens()->delete();

        return response()->json([
            'success' => true,
            'message' => 'You have been successfully logged out!',
        ], 200);
    }
}

#5 Configure Routes for Laravel Vue.js Authentication

Open the routes/web.php file and add the below routes to it. When we use the API routes we need to update the web routes to something like this.

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/

Route::get('{any}', function () {
    return view('app');
})->where('any', '.*');

// Route::get('/', function () {
//     return view('welcome');
// });

Now, open the routes/api.php file and update the Laravel Vue authentication system.

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\AuthController;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::post('/user', [AuthController::class, 'user']);
    Route::post('/logout', [AuthController::class, 'logout']);
});

#6 Install npm Pacakges

Great! The backend work is completed now you need to install npm packages to create vue.js crud in Laravel app.

First, you need to generate laravel ui package, it will manifest vue laravel scaffold.

composer require laravel/ui
php artisan ui vue

Now install the npm package and then run the dev command it will reinitialize the components.

npm install && npm run dev

Next, install the vue axios using the below command.

npm install vue-axios

In Vue 2 to install Vue Router you can use npm install vue-router but we are using vue 4 version. So we will go with this command.

npm i vue-router@next

If you want to use Bootstrap then you can install the Bootstrap package using the below command.

npm install bootstrap

#7 Initiate Vue in Laravel

Now we will see how to connect blade files with vue resources js.

For this, you need to create an app.blade.php inside the resources/views directory. So, open the resources/views/app.blade.php file and update the below code on it.

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Laravel Vue.js Authentication using Laravel 11 sanctum and Vite</title>

	@vite('resources/css/app.css')
</head>
<body>
	<div id="app"></div>

	@vite('resources/js/app.js')
</body>
</html>

#8 Create Vue Js Authentication Components

Next, you’ll want to include the App.vue file within the resources/js directory. This file serves as the entry point for displaying menus and initializing the router view.

  1. Create a new file named App.vue in the resources/js folder.
  2. Open the resources/js/App.vue file and insert the following code:
<template>
  <div class="container">
      <nav class="navbar navbar-expand-lg navbar-light bg-light">
          <div class="collapse navbar-collapse">
              <div class="navbar-nav">
                  <router-link :to="{ name: 'Login' }"  class="nav-item nav-link">Login</router-link>
                  <router-link :to="{ name: 'Register' }"  class="nav-item nav-link">Register</router-link>
              </div>
          </div>
      </nav>
      <router-view> </router-view>
  </div>
</template>

Now you need to create the vue.js authentication components. So first create a “auth” folder inside the “resources/js/components/ directory. Next, you need to create the below components inside resources/js/components directory.

  • Login.vue
  • Register.vue

First open resources\js\components\auth\Register.vue file and put the below code on it.

<template>
  <div class="container mt-5">
    <h1>Register</h1>
    <div class="mb-3">
      <label for="name" class="form-label">Name</label>
      <input type="text" class="form-control" id="name" v-model="user.name">
      <span v-if="errors.name" class="text-danger">{{ errors.name }}</span>
    </div>
    <div class="mb-3">
      <label for="email" class="form-label">Email</label>
      <input type="email" class="form-control" id="email" v-model="user.email">
      <span v-if="errors.email" class="text-danger">{{ errors.email }}</span>
    </div>
    <div class="mb-3">
      <label for="password" class="form-label">Password</label>
      <input type="password" class="form-control" id="password" v-model="user.password">
      <span v-if="errors.password" class="text-danger">{{ errors.password }}</span>
    </div>
    <div class="mb-3">
      <label for="password_confirmation" class="form-label">Confirm Password</label>
      <input type="password" class="form-control" id="password_confirmation" v-model="user.password_confirmation">
    </div>
    <button type="button" class="btn btn-primary" @click="register()">Register</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        name: '',
        email: '',
        password: '',
        password_confirmation: '',
      },
      errors: {}
    };
  },
  methods: {
    register() {
      this.axios.post('/api/register', this.user)
        .then(({ data }) => {
          this.$router.push('/login');
        })
        .catch((error) => {
          console.log(error);
          if (error.response && error.response.status === 422) {
            const responseData = error.response.data;
            this.errors = {};
            for (const field in responseData.errors) {
              if (Object.prototype.hasOwnProperty.call(responseData.errors, field)) {
                this.errors[field] = responseData.errors[field][0];
              }
            }
          }
        });
    }
  }
}
</script>

First open resources\js\components\auth\Login.vue file and put the below code on it.

<template>
  <div class="container mt-5">
    <h1>Login</h1>
    <div class="mb-3">
      <label for="email" class="form-label">Email</label>
      <input type="email" class="form-control" id="email" v-model="user.email">
      <span v-if="errors.email" class="text-danger">{{ errors.email }}</span>
    </div>
    <div class="mb-3">
      <label for="password" class="form-label">Password</label>
      <input type="password" class="form-control" id="password" v-model="user.password">
      <span v-if="errors.password" class="text-danger">{{ errors.password }}</span>
    </div>
    <span v-if="errors.other" class="text-danger">{{ errors.other }}</span>
    <button type="button" class="btn btn-primary" @click="login">Login</button>
  </div>
</template>
<script>
  export default {
      data() {
          return {
              user: {
                  email: '',
                  password: '',
              },
              errors: {}
          };
      },
      methods: {
          login() {
            this.axios.post('/api/login', this.user)
                  .then(({data}) => {
                      console.log(data);
                      localStorage.setItem('token', data.access_token);
                      localStorage.setItem('user', JSON.stringify(data.user));

                      this.$router.push('/dashboard');
                  })
                  .catch((error) => {
                      console.log(error);
                      if (error.response && error.response.status === 422) {
                        const responseData = error.response.data;
                        this.errors = {};
                        for (const field in responseData.errors) {
                          if (Object.prototype.hasOwnProperty.call(responseData.errors, field)) {
                            this.errors[field] = responseData.errors[field][0];
                          }
                        }
                      }
                  });
          }
      }
  }
</script>

Next, create Dashboard.vue inside the resources/js/components directory. Now, open resources\js\components\Dashboard.vue file and put the below code on it.

<template>
  <div>
      <h1>Dashboard Dashboard</h1>
      <button class="btn btn-primary" @click="logout()">Logout</button>
      <h4>The logged in user details Here</h4><br>
      <p> {{user.name}}</p>
      <p> {{user.email}}</p>
  </div>
</template>
<script>
  export default {
      data () {
          return {
            user: JSON.parse(localStorage.getItem('user')) || {}
          }
      },
      methods: {
          logout() {
            this.axios.post('/api/logout')
                .then(({data}) => {
                    localStorage.removeItem('token');
                    localStorage.removeItem('user');
                    this.$router.push('/login');
                })
                .catch((error) => {
                    console.log(error);
                });
          }
      },
  }
</script>

#9 Create Vue Routes for Laravel Vue Authentication

Now we create new file “routes.js” inside “resources/js” directory. Next, open the resources/js/routes.js file and place the below code on it.

import { createWebHistory, createRouter } from "vue-router";

import Dashboard from "@/components/Dashboard.vue";
import Login from "@/components/auth/Login.vue";
import Register from "@/components/auth/Register.vue";

const routes = [
    {
        path: "/login",
        name: "Login",
        component: Login,
    },
    {
        path: "/register",
        name: "Register",
        component: Register,
    },
    {
        path: "/dashboard",
        name: "Dashboard",
        component: Dashboard,
        meta: {
            requiresAuth: true
        }
    },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.requiresAuth) ) {
        const token = localStorage.getItem('token');
        if (token) {
            next();
            return;
        } else {
            router.push('/login');
        }
    } else {
        next();
    }
});

export default router;

#10 Include Dependencies to app.js

So, open the resources\js\app.js file and update your packages like this.

import './bootstrap';

import 'bootstrap/dist/css/bootstrap.min.css';

import { createApp } from 'vue';
import App from './App.vue';
import axios from './axios';

import VueAxios from 'vue-axios'
import router from './routes'

// import ExampleComponent from './components/ExampleComponent.vue';
// app.component('example-component', ExampleComponent);

createApp(App).use(router).use(VueAxios, axios).mount('#app');

#11 Send Authorization token in Headers of every required Authenticate Request

import axios from 'axios';

// axios.defaults.baseURL = 'https://api.example.com';

axios.interceptors.request.use(function (config) {
    const token = localStorage.getItem('token');

    if (token) {
        config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
}, function (error) {
    return Promise.reject(error);
});

export default axios;

#12 Vite config Code Should be (vite.config.js)

Before Laravel utilized Mix to minify JavaScript and CSS (SCSS), but in the current version, Vite is employed for this purpose. To ensure consistency, open the vite.config.js file and verify that the code remains unchanged.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
            ],
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
    resolve: {
        alias: {
            vue: 'vue/dist/vue.esm-bundler.js',
        },
    },
});

#11 Run Development Server

After fulfilling all the requirements for the Laravel Vue.js 3 API authentication application, proceed by running the following commands: npm run dev and php artisan serve.

Previously, Laravel provided the watch command for this purpose. However, with the introduction of Vite, the script has been updated to dev.

The dev command initializes our JavaScript components, while the serve command runs our application.

npm run dev
php artisan serve

Now, hit the below ur and enjoy.

http://localhost:8000/
or
http://127.0.0.1:8000/

So, guys the Laravel 11 Vuejs 3 API authentication Example is completed now.