Schedule A Consultation

    Fields marked * are mandatory

      CA INQUIRY

      SPA with role-based auth with Laravel & Vue.js

      This entry was posted on Friday March 13, 2020

      Laravel Installation

      In a terminal, run the following command to install Laravel project:

      laravel new laravel-vue-spa-cspl

      composer global require laravel/installer

      Creation of users and roles

      Once laravel installed, let’s add roles to users and create some test users.

      For the roles, I’m just gonna use a ‘role’ field in the users table, but any role manager package could do the trick.
      In this example, a user will have role ‘1’ and an administrator, role ‘2’.

      Add the following line to the ‘create_users_table’ migration:

      $table->integer(‘role’)->default(1);

      In order to simplify this example as much as possible, I added the ‘nullable()’ option to the ‘name’ field. this allow me not to provide a name when registering:

      $table->string(‘name’)->nullable();

      Update DatabaseSeeder.php file to create an user and an administrator:

      <?php

      use App\User;
      use Illuminate\Database\Seeder;
      use Illuminate\Support\Facades\Hash;class DatabaseSeeder extends Seeder
      {
          public function run()
          {
              User::create([
                  ‘name’ => ‘Admin’,
                  ’email’ => ‘admin@cilected.com’,
                  ‘password’ => Hash::make(‘cspl@123’),
                  ‘role’ => 2
              ]);   User::create([
                  ‘name’ => ‘User’,
                  ’email’ => ‘user@cilected.com’,
                  ‘password’ => Hash::make(‘cspl@123’),
                  ‘role’ => 1
              ]);
          }
      }

      After configuring database access in the ‘.env’ file, run the following command to create the ‘users’ table with the two defined users:

      php artisan migrate –seed

      API route protection with JWT

      I’ll use tymondesigns/jwt-auth package to handle API authentication. in this example, I use the ‘dev-develop’ version of this package.

      composer require tymon/jwt-auth:dev-develop

      Publish JWT configuration with the following command:

      php artisan vendor:publish –provider=”Tymon\JWTAuth\Providers\LaravelServiceProvider”

      This will create ‘config/jwt.php’ file.

      Then, create JWT secret key with this command:

      php artisan jwt:secret

      This will generate an environment varaible in the ‘.env’ file.

      Then, in the ‘config/auth.php’ file, replace default guard by ‘api’:

      ‘defaults’ => [
          ‘guard’ => ‘api’,
          ‘passwords’ => ‘users’,
      ],

      Then the driver of the API guard by ‘jwt’:

      ‘guards’ => [
          ‘web’ => [
              ‘driver’ => ‘session’,
              ‘provider’ => ‘users’,
          ],   ‘api’ => [
              ‘driver’ => ‘jwt’,
              ‘provider’ => ‘users’,
          ],
      ],

      modify the User model to implement ‘JWTSubject’ interface:

      <?php

      namespace App;

      use Illuminate\Notifications\Notifiable;
      use Illuminate\Contracts\Auth\MustVerifyEmail;
      use Illuminate\Foundation\Auth\User as Authenticatable;
      use Tymon\JWTAuth\Contracts\JWTSubject;class User extends Authenticatable implements JWTSubject
      {
          use Notifiable;    /**
          * The attributes that are mass assignable.
          *
          * @var array
          */
          protected $fillable = [
              ‘name’, ’email’, ‘password’,
          ];   /**
          * The attributes that should be hidden for arrays.
          *
          * @var array
          */
          protected $hidden = [
              ‘password’, ‘remember_token’,
          ];
         
          public function getJWTIdentifier()
          {
              return $this->getKey();
          }
         
          public function getJWTCustomClaims()
          {
              return [];
          }
      }

      Note : don’t forget to add getJWTIdentifier() and getJWTCustomClaims() methods that are required by the interface.

      In the ‘routes/api.php’ file, the already existing ‘api/user’ route uses the ‘auth:api’ middleware, now configured with JWT.

      If we try to access this route in the browser (http://127.0.0.1:8000/api/user), access will be refused and we’ll get an error page with this message: ‘Route [login] not defined.’

      Note : to access the application, launch the internal Laravel server with the ‘php artisan serve’ command. Default URL is ‘http://127.0.0.1:8000’.

      This behaviour is defined in the ‘app/Http/Middleware/Authenticate.php’ file. As the point is to set up an API that send json formatted responses, and that the login page will be handled by Vue, it’s possible (but optionnal) to modify this file to send a json formatted response:

      <?php

      namespace App\Http\Middleware;

      use Closure;
      use Illuminate\Auth\Middleware\Authenticate as Middleware;class Authenticate extends Middleware
      {
          public function handle($request, Closure $next, …$guards)
          {
              if ($this->authenticate($request, $guards) === ‘authentication_error’) {
                  return response()->json([‘error’=>’Unauthorized’]);
              } return $next($request);
          } protected function authenticate($request, array $guards)
          {
              if (empty($guards)) {
                  $guards = [null];
              } foreach ($guards as $guard) {
                  if ($this->auth->guard($guard)->check()) {
                      return $this->auth->shouldUse($guard);
                  }
              } return ‘authentication_error’;
          }
      }

      Application screenshot on the ‘api/users/2’ route after Authenticate.php file modification

      Authentication endpoints creation

      We will now create authentication endpoints. In the ‘routes/api.php’ file, add the following lines:

      Route::prefix(‘auth’)->group(function () {
          Route::post(‘register’, ‘AuthController@register’);
          Route::post(‘login’, ‘AuthController@login’);
          Route::get(‘refresh’, ‘AuthController@refresh’);    Route::group([‘middleware’ => ‘auth:api’], function(){
              Route::get(‘user’, ‘AuthController@user’);
              Route::post(‘logout’, ‘AuthController@logout’);
          });
      });

      The ‘api/auth/register’ route will be used to create users.
      The ‘api/auth/login’ route will be used to login.
      The ‘api/auth/refresh’ route will be used to refresh token.

      Thoses three routes are public.

      The ‘api/auth/user’ route will be used to fetch user’s informations.
      The ‘api/auth/logout’ route will be used to logout.

      Thoses tow routes are reachable by connected users only.

      Then, we need to create the controller that will handle these requests:

      php artisan make:controller AuthController

      This will create the ‘app/Http/Controllers/AuthController.php’ file.

      Add required methods as presented below:

      <?php

      namespace App\Http\Controllers;

      use App\User;
      use Illuminate\Http\Request;
      use Illuminate\Support\Facades\Auth;
      use Illuminate\Support\Facades\Validator;

      class AuthController extends Controller
      {    public function register(Request $request)
          {
              $v = Validator::make($request->all(), [
                  ’email’ => ‘required|email|unique:users’,
                  ‘password’ => ‘required|min:3|confirmed’,
              ]);   if ($v->fails())
              {
                  return response()->json([
                      ‘status’ => ‘error’,
                      ‘errors’ => $v->errors()
                  ], 422);
              } $user = new User;
              $user->email = $request->email;
              $user->password = bcrypt($request->password);
              $user->save();        return response()->json([‘status’ => ‘success’], 200);
          } public function login(Request $request)
          {
              $credentials = $request->only(’email’, ‘password’);        if ($token = $this->guard()->attempt($credentials)) {
                  return response()->json([‘status’ => ‘success’], 200)->header(‘Authorization’, $token);
              } return response()->json([‘error’ => ‘login_error’], 401);
          } public function logout()
          {
              $this->guard()->logout();        return response()->json([
                  ‘status’ => ‘success’,
                  ‘msg’ => ‘Logged out Successfully.’
              ], 200);
          } public function user(Request $request)
          {
              $user = User::find(Auth::user()->id);        return response()->json([
                  ‘status’ => ‘success’,
                  ‘data’ => $user
              ]);
          } public function refresh()
          {
              if ($token = $this->guard()->refresh()) {
                  return response()
                      ->json([‘status’ => ‘successs’], 200)
                      ->header(‘Authorization’, $token);
              } return response()->json([‘error’ => ‘refresh_token_error’], 401);
          } private function guard()
          {
              return Auth::guard();
          }
      }

      Note: For the sake of simplification, I am doing very little checking here. I use a validator on the ‘register()’ method as an example, but in real use cases, it should also be done for the ‘login()’ method and use ‘try / catch’ blocks to manage other error cases (server, database, …).

      The ‘register()’ method is quite simple, it merely creates a user with the returned fields, taking care of hashing the password before.

      The ‘login()’ method uses the Auth::guard() method that uses JWT.
      Thus, the ‘attempt()’ method which checks the provided credentials will generate a token which will be returned in the headers of the response if successful.

      The ‘logout()’ method will be used to disconnect users by disabling the token.

      The ‘user()’ method will retrieve the logged user’s informations and send it back to the response dataset.

      The ‘refresh()’ method will refresh the token if it has expired. It is possible to define the duration of validity of the token in the file ‘config / jwt.php’.

      Endpoints protection by role

      We will now create two middlewares which purpose will be to allow access to API resources depending on the user’s role.

      For example, an administrator should be able to access the list of all users, while a user should only be able to access his own informations.

      Let’s create the middlewares:

      php artisan make:middleware CheckIsAdmin
      php artisan make:middleware CheckIsAdminOrSelf

      This will create two files in ‘app/http/Middleware’.

      in the first one, ‘CheckIsAdmin.php’, we will do a simple check of wheter the logged user has admin role. Replace file content by this code:

      <?php

      namespace App\Http\Middleware;

      use Closure;
      use Illuminate\Support\Facades\Auth;class CheckIsAdmin
      {
          public function handle($request, Closure $next)
          {
              if(Auth::user()->role === 2) {
                  return $next($request);
              } else {
                  return response()->json([‘error’ => ‘Unauthorized’], 403);
              }
          }
      }

      In the other middleware, ‘CheckIsAdminOrSelf.php’, we will check that the user is an admin or the the resource he seeks to consult or update concerns him. Replace file content by this code:

      <?php

      namespace App\Http\Middleware;

      use Closure;
      use Illuminate\Support\Facades\Auth;class CheckIsAdminOrSelf
      {
          public function handle($request, Closure $next)
          {
              $requestedUserId = $request->route()->parameter(‘id’);        if(
                  Auth::user()->role === 2 ||
                  Auth::user()->id == $requestedUserId
              ) {
                  return $next($request);
              } else {
                  return response()->json([‘error’ => ‘Unauthorized’], 403);
              }
          }
      }

      Now let’s declare these middlewares in the ‘app/Http/kernel.php’ file. Add the following lines to the ‘$routeMiddleware’ array:

      ‘isAdmin’ => \App\Http\Middleware\CheckIsAdmin::class,
      ‘isAdminOrSelf’ => \App\Http\Middleware\CheckIsAdminOrSelf::class,

      Then, we will create routes to access user’s informations and protect them with the appropriate middlewares:

      Route::group([‘middleware’ => ‘auth:api’], function(){
          // Users
          Route::get(‘users’, ‘UserController@index’)->middleware(‘isAdmin’);
          Route::get(‘users/{id}’, ‘UserController@show’)->middleware(‘isAdminOrSelf’);
      });

      Finally, we will create the controller for users and define ‘index()’ and ‘show()’ methods.

      php artisan make:controller UserController

      This will generate the ‘app/Http/Controllers/UserController.php’ file of which here is the content:

      <?php

      namespace App\Http\Controllers;

      use App\User;
      use Illuminate\Http\Request;class UserController extends Controller
      {
          public function index()
          {
              $users = User::all();        return response()->json(
                  [
                      ‘status’ => ‘success’,
                      ‘users’ => $users->toArray()
                  ], 200);
          } public function show(Request $request, $id)
          {
              $user = User::find($id);        return response()->json(
                  [
                      ‘status’ => ‘success’,
                      ‘user’ => $user->toArray()
                  ], 200);
          }
      }

      Thus, the ‘api/users’ endpoint will only be accessible for administrators, and the ‘api/users/2’ endpoint will only be accessible for administrators and the user with id 2.

      Setup front with Vue.js

      We will start by configuring Laravel for using Vue. Vue is already installed but we have to implement its usage.

      First, install front dependencies with this command:

      npm install

      Then, open the ‘resources/views/welcome.blade.php’ file and replace its content by the following code:

      <!DOCTYPE html>
      <html lang=”{{ str_replace(‘_’, ‘-‘, app()->getLocale()) }}”>
      <head>
        <meta charset=”utf-8″>
        <meta http-equiv=”X-UA-Compatible” content=”IE=edge”>
        <meta name=”viewport” content=”width=device-width, initial-scale=1″>  <!– CSRF Token –>
        <meta name=”csrf-token” content=”{{ csrf_token() }}”>  <title>{{ config(‘app.name’, ‘Laravel’) }}</title> <!– Scripts –>
        <script src=”{{ asset(‘js/app.js’) }}” defer></script>  <!– Fonts –>
        <link rel=”dns-prefetch” href=”https://fonts.gstatic.com”>
        <link href=”https://fonts.googleapis.com/css?family=Raleway:300,400,600″ rel=”stylesheet” type=”text/css”>  <!– Styles –>
        <link href=”{{ asset(‘css/app.css’) }}” rel=”stylesheet”>
      </head>
      <body>
      <div id=”app”>
        <index></index>
      </div>
      </body>
      </html>

      Note : What matters here is inclusion of script ‘js/app.js’, the ‘<div id=”app”>’ tag and the ‘<index>’ tag.

      then create ‘resources/js/Index.vue’ file and add following code:

      <template>
          <div id=”main”>
              <header id=”header”>
                  <h1>
                      Laravel Vue SPA
                  </h1>
              </header>
              <div id=”content”>
                  Bienvenue !
              </div>
          </div>
      </template><script>
        export default {
          data() {
            return {
              //
            }
          },
          components: {
              //
          }
        }
      </script>

      To compile js files as creating the interface with Vue, run the following command:

      npm run watch

      This will run a continuous script that will compile files each time they are saved.

      Open the ‘resources/js/app.js’ file and replace its content by the following code:

      import ‘./bootstrap’
      import Vue from ‘vue’
      import Index from ‘./Index’// Set Vue globally
      window.Vue = Vue// Load Index
      Vue.component(‘index’, Index)const app = new Vue({
        el: ‘#app’
      });

      After compilation, by accessign application in browser, you should see elements defined if the ‘Index.vue’ file.

      Setup front-side authentication

      To handle authnetication and right access front-side, we will use the package ‘websanova/vue-auth’ as well as several other dependencies: ‘vue-router’, ‘vue-axios’, ‘axios’ and ‘es6-promise’.

      Install dependancies with the following command:

      npm i @websanova/vue-auth vue-router vue-axios axios es6-promise

      Create ‘resources/js/auth.js’ file and insert the following code:

      import bearer from ‘@websanova/vue-auth/drivers/auth/bearer’
      import axios from ‘@websanova/vue-auth/drivers/http/axios.1.x’
      import router from ‘@websanova/vue-auth/drivers/router/vue-router.2.x’// Auth base configuration some of this options
      // can be override in method calls
      const config = {
        auth: bearer,
        http: axios,
        router: router,
        tokenDefaultName: ‘laravel-vue-spa’,
        tokenStore: [‘localStorage’],
        rolesVar: ‘role’,
        registerData: {url: ‘auth/register’, method: ‘POST’, redirect: ‘/login’},
        loginData: {url: ‘auth/login’, method: ‘POST’, redirect: ”, fetchUser: true},
        logoutData: {url: ‘auth/logout’, method: ‘POST’, redirect: ‘/’, makeRequest: true},
        fetchData: {url: ‘auth/user’, method: ‘GET’, enabled: true},
        refreshData: {url: ‘auth/refresh’, method: ‘GET’, enabled: true, interval: 30}
      }export default config

      This file is used for ‘vue-auth’ configuration. Some clarifications:
      – ‘rolesVar’ is used to determine which user model field is used to define the user’s role. If you nammed your field differently or use a package to handle roles in Laravel, modify this value consequently.
      – ‘registerData’, ‘loginData’, ‘logoutData’, ‘fetchData’ and ‘refreshData’ are used to define API endpoints that Vue-Auth is gonna use. Here I define endpoints previously created. You will find more informations about Vue-Auth configuration on the official documentation 🙂

      Create the ‘resources/js/router.js’ file and insert the following code:

      import VueRouter from ‘vue-router’// Pages
      import Home from ‘./pages/Home’
      import Register from ‘./pages/Register’
      import Login from ‘./pages/Login’
      import Dashboard from ‘./pages/user/Dashboard’
      import AdminDashboard from ‘./pages/admin/Dashboard’// Routes
      const routes = [
        {
          path: ‘/’,
          name: ‘home’,
          component: Home,
          meta: {
            auth: undefined
          }
        },
        {
          path: ‘/register’,
          name: ‘register’,
          component: Register,
          meta: {
            auth: false
          }
        },
        {
          path: ‘/login’,
          name: ‘login’,
          component: Login,
          meta: {
            auth: false
          }
        },
        // USER ROUTES
        {
          path: ‘/dashboard’,
          name: ‘dashboard’,
          component: Dashboard,
          meta: {
            auth: true
          }
        },
        // ADMIN ROUTES
        {
          path: ‘/admin’,
          name: ‘admin.dashboard’,
          component: AdminDashboard,
          meta: {
            auth: {roles: 2, redirect: {name: ‘login’}, forbiddenRedirect: ‘/403’}
          }
        },
      ]const router = new VueRouter({
        history: true,
        mode: ‘history’,
        routes,
      })export default router

      This file is used to define the differents routes of the application, and which components are used for each one of them. The ‘meta’ parameter is used to define the access rules for each route.
      – ‘auth:undefined’ will be used for public routes;
      – ‘auth:true’ will be used for routes only accessible for connected users;
      – ‘auth:false’ will be used for routes only accessible for unconnected users;
      – ‘auth: {roles: 2, …}’ will be used for routes only accessible for an administrator. In this object, we can provide redirection rules if the user is not conected or if he has’nt the rights to access.

      Let’s create theses components.

      File ‘resources/js/pages/Home.vue’:

      <template>
          <div class=”container”>
              <div class=”card card-default”>
                  <div class=”card-header”>Bienvenue</div>            <div class=”card-body”>
                      <p>
                          American Main Barbary Coast scuttle hardtack spanker fire ship grapple jack code  of conduct port. Port red ensign Shiver me timbers provost salmagundi bring a spring upon her cable pillage cog crow’s nest lateen sail.  Barbary Coast quarterdeck lass coffer keel hulk mizzen me square-rigged loot.
                      </p>
                      <p>
                          Yardarm starboard keelhaul list schooner prow booty cackle  fruit gabion topmast. Plunder shrouds Nelsons folly jack Arr parley warp  grog blossom ballast pressgang. Knave crack Jennys tea cup flogging log man-of-war hearties killick long clothes six pounders hulk.
                      </p>
                  </div>
              </div>
          </div>
      </template>

      This file will be application homepage.

      File ‘resources/js/pages/Register.vue’:

      <template>
          <div class=”container”>
              <div class=”card card-default”>
                  <div class=”card-header”>Inscription</div>            <div class=”card-body”>
                      <div class=”alert alert-danger” v-if=”has_error && !success”>
                          <p v-if=”error == ‘registration_validation_error'”>Erreur(s) de validation, veuillez consulter le(s) message(s) ci-dessous.</p>
                          <p v-else>Erreur, impossible de s’inscrire pour le moment. Si le problème persiste, veuillez contacter un administrateur.</p>
                      </div>       <form autocomplete=”off” @submit.prevent=”register” v-if=”!success” method=”post”>                    <div class=”form-group” v-bind:class=”{ ‘has-error’: has_error && errors.email }”>
                              <label for=”email”>E-mail</label>
                              <input type=”email” id=”email” class=”form-control” placeholder=”user@example.com” v-model=”email”>
                              <span class=”help-block” v-if=”has_error && errors.email”>{{ errors.email }}</span>
                          </div>       <div class=”form-group” v-bind:class=”{ ‘has-error’: has_error && errors.password }”>
                              <label for=”password”>Mot de passe</label>
                              <input type=”password” id=”password” class=”form-control” v-model=”password”>
                              <span class=”help-block” v-if=”has_error && errors.password”>{{ errors.password }}</span>
                          </div>       <div class=”form-group” v-bind:class=”{ ‘has-error’: has_error && errors.password }”>
                              <label for=”password_confirmation”>Confirmation mot de passe</label>
                              <input type=”password” id=”password_confirmation” class=”form-control” v-model=”password_confirmation”>
                          </div>       <button type=”submit” class=”btn btn-default”>Inscription</button>
                      </form>
                  </div>
              </div>
          </div>
      </template>
      <script>
        export default {
          data() {
            return {
              name: ”,
              email: ”,
              password: ”,
              password_confirmation: ”,
              has_error: false,
              error: ”,
              errors: {},
              success: false
            }
          },   methods: {
            register() {
              var app = this
              this.$auth.register({
                data: {
                  email: app.email,
                  password: app.password,
                  password_confirmation: app.password_confirmation
                },
                success: function () {
                  app.success = true
                  this.$router.push({name: ‘login’, params: {successRegistrationRedirect: true}})
                },
                error: function (res) {
                  console.log(res.response.data.errors)
                  app.has_error = true
                  app.error = res.response.data.error
                  app.errors = res.response.data.errors || {}
                }
              })
            }
          }
        }
      </script>

      Note : I handle errors only in this component, in the same way I only managed API validation in the ‘register()’ method. In real use cases, errors have to be handled in every components!

      File ‘resources/js/pages/Login.vue’:

      <template>
          <div class=”container”>
              <div class=”card card-default”>
                  <div class=”card-header”>Connexion</div>            <div class=”card-body”>
                      <div class=”alert alert-danger” v-if=”has_error”>
                          <p>Erreur, impossible de se connecter avec ces identifiants.</p>
                      </div>
                      <form autocomplete=”off” @submit.prevent=”login” method=”post”>
                          <div class=”form-group”>
                              <label for=”email”>E-mail</label>
                              <input type=”email” id=”email” class=”form-control” placeholder=”user@example.com” v-model=”email” required>
                          </div>
                          <div class=”form-group”>
                              <label for=”password”>Mot de passe</label>
                              <input type=”password” id=”password” class=”form-control” v-model=”password” required>
                          </div>
                          <button type=”submit” class=”btn btn-default”>Connexion</button>
                      </form>
                  </div>
              </div>
          </div>
      </template><script>
        export default {
          data() {
            return {
              email: null,
              password: null,
              has_error: false
            }
          },   mounted() {
            //
          },   methods: {
            login() {
              // get the redirect object
              var redirect = this.$auth.redirect()
              var app = this
              this.$auth.login({
                params: {
                  email: app.email,
                  password: app.password
                },
                success: function() {
                  // handle redirection
                  const redirectTo = redirect ? redirect.from.name : this.$auth.user().role === 2 ? ‘admin.dashboard’ : ‘dashboard’            this.$router.push({name: redirectTo})
                },
                error: function() {
                  app.has_error = true
                },
                rememberMe: true,
                fetchUser: true
              })
            }
          }
        }
      </script>

      File ‘resources/js/pages/user/Dashboard.vue’:

      <template>
          <div class=”container”>
              <div class=”card card-default”>
                  <div class=”card-header”>Dashboard</div>            <div class=”card-body”>
                      Bienvenue
                  </div>
              </div>
          </div>
      </template><script>  export default {
          data() {
            return {
              //
            }
          },
          components: {
            //
          }
        }
      </script>

      File ‘resources/js/pages/admin/Dashboard.vue’:

      <template>
          <div class=”container”>
              <div class=”card card-default”>
                  <div class=”card-header”>Admin Dashboard</div>            <div class=”card-body”>
                      Bienvenue sur votre dashboard administrateur
                  </div>
              </div>
          </div>
      </template><script>
        export default {
          mounted() {
            //
          }
        }
      </script>

      We will now modify the ‘resources/js/Index.vue’ file and replace the string “Bienvenue” by the ‘<router-view>’ component that will display component depending on the requested route. The ‘/’ route calls the ‘Home’ component that will be displayed on the homepage.

      The ‘Index.vue’ file looks like this now:

      <template>
          <div id=”main”>
              <header id=”header”>
                  <h1>
                      Laravel Vue SPA
                  </h1>
              </header>
              <div id=”content”>
                  <router-view></router-view>
              </div>
          </div>
      </template><script>
        export default {
          data() {
            return {
              //
            }
          },
          components: {
              //
          }
        }
      </script>

      Finally, we have to update the ‘resources/js/app.js’ file in order to implement ‘vue-auth’ and ‘vue-router’:

      import ‘es6-promise/auto’
      import axios from ‘axios’
      import ‘./bootstrap’
      import Vue from ‘vue’
      import VueAuth from ‘@websanova/vue-auth’
      import VueAxios from ‘vue-axios’
      import VueRouter from ‘vue-router’
      import Index from ‘./Index’
      import auth from ‘./auth’
      import router from ‘./router’// Set Vue globally
      window.Vue = Vue// Set Vue router
      Vue.router = router
      Vue.use(VueRouter)// Set Vue authentication
      Vue.use(VueAxios, axios)
      axios.defaults.baseURL = `${process.env.MIX_APP_URL}/api`
      Vue.use(VueAuth, auth)// Load Index
      Vue.component(‘index’, Index)const app = new Vue({
        el: ‘#app’,
        router
      });

      Note : The varaible ‘axios.defaults.baseURL’ uses the environment variable ‘MIX_APP_URL’. We have to define this variable in the ‘.env’ file.

      MIX_APP_URL=”${APP_URL}”

      this variable uses itself the ‘APP_URL’ variable useful for Laravel.
      We have to define this variable, I use the URL I get from the ‘php artisan serve’ command, that is by default ‘http://127.0.0.1:8000’.

      APP_URL=http://127.0.0.1:8000

      If you use a virtual host, modify this value consequently.

      Thus, when compiling .js files with ‘npm run dev’ or ‘npm run watch’ commands, all environment variables starting with ‘MIX_’ will be taken into account by the front.

      If you refresh your homepage, you should see the content of the ‘Home.vue’ component.

       

      Application screenshot

      However, if you try to access the ‘/login’ page, you should have an error. Indeed, Laravel is currently configured to return only one route, the index, which displys the template ‘welcome.blade.php’.

      To solve this, we have to edit the ’routes/web.php’ file and add these lines:

      // Route to handle page reload in Vue except for api routes
      Route::get(‘/{any?}’, function (){
          return view(‘welcome’);
      })->where(‘any’, ‘^(?!api\/)[\/\w\.-]*’);

      Thus, any URL will return the ‘welcome’ view and vue-router will display the corresponding component.

      The last line is for the API routes not to be taken in account, which would break the application.

      If you try to access the ‘/login’ page, you should now see the login form. And if you try to access ‘/dashboard’ or ‘/admin/dashboard’ you should be redirect to the login page.

       

      Application screenshot — login page

      If you log in, you should acces to the dashboard corresponding to your role.
      This behaviour is handled in the ‘Login.vue’ component, line 52:

      const redirectTo = redirect ? redirect.from.name : this.$auth.user().role === 2 ? ‘admin.dashboard’ : ‘dashboard’

      this line allows:
      – either to redirect the user to the page he came before, if he had been redirected to the login page. This is handled by the ‘redirect’ variable declared few ines before with ‘this.$auth.redirect()’. This Vue-Auth method allows to know if the user has been redirected after attempt to access a restricted page;
      – either to redirect to the corresponding dashbord depending on the role.

      Add navigation menu

      The application is functional, now let’s add a navigation menu.

      Create the ‘resources/js/components/Menu.Vue’ file and insert the following code:

      <template>
          <nav id=”nav”>
              <ul>
                  <!–UNLOGGED–>
                  <li v-if=”!$auth.check()” v-for=”(route, key) in routes.unlogged” v-bind:key=”route.path”>
                      <router-link :to=”{ name : route.path }” :key=”key”>
                          {{route.name}}
                      </router-link>
                  </li>
                  <!–LOGGED USER–>
                  <li v-if=”$auth.check(1)” v-for=”(route, key) in routes.user” v-bind:key=”route.path”>
                      <router-link :to=”{ name : route.path }” :key=”key”>
                          {{route.name}}
                      </router-link>
                  </li>
                  <!–LOGGED ADMIN–>
                  <li v-if=”$auth.check(2)” v-for=”(route, key) in routes.admin” v-bind:key=”route.path”>
                      <router-link :to=”{ name : route.path }” :key=”key”>
                          {{route.name}}
                      </router-link>
                  </li>
                  <!–LOGOUT–>
                  <li v-if=”$auth.check()”>
                      <a href=”#” @click.prevent=”$auth.logout()”>Logout</a>
                  </li>
              </ul>
          </nav>
      </template><script>
        export default {
          data() {
            return {
              routes: {
                // UNLOGGED
                unlogged: [
                  {
                    name: ‘Inscription’,
                    path: ‘register’
                  },
                  {
                    name: ‘Connexion’,
                    path: ‘login’
                  }
                ],   // LOGGED USER
                user: [
                  {
                    name: ‘Dashboard’,
                    path: ‘dashboard’
                  }
                ],
                // LOGGED ADMIN
                admin: [
                  {
                    name: ‘Dashboard’,
                    path: ‘admin.dashboard’
                  }
                ]
              }
            }
          },
          mounted() {
            //
          }
        }
      </script>

      Then in the ‘resources/js/index.vue’ file, replace the content by the following code:

      <template>
          <div id=”main”>
              <header id=”header”>
                  <h1>
                      <router-link :to=”{name: ‘home’}”>
                          Laravel Vue SPA
                      </router-link>
                  </h1>
                  <navigationMenu></navigationMenu>
              </header>
              <div id=”content”>
                  <router-view></router-view>
              </div>
          </div>
      </template><script>
        import navigationMenu from ‘./components/Menu.vue’
        export default {
          data() {
            return {
              //
            }
          },
          components: {
            navigationMenu
          }
        }
      </script>

      We import the navigation component to display it under the title in the header. On importe le composant de navigation créé pour l’afficher en dessous du titre dans le header. We also add a link on the title, with the ‘router-link’ tag, to redirect to the home page.

       

      Application screenshot

      Fetch API data with axios

      Now that authentication system is setup in back and in front, let’s fetch some data from the API to display them in Vue.

      We will get the users list to disply it in the admin dashboard.

      Create the ‘resources/js/components/user-list.vue’ file and insert the followin code:

      <template>
      <div>
          <h3>Liste de utilisateurs</h3>
          <div class=”alert alert-danger” v-if=”has_error”>
              <p>Erreur, impossible de récupérer la liste des utilisateurs.</p>
          </div>    <table class=”table”>
              <tr>
                  <th scope=”col”>Id</th>
                  <th scope=”col”>Nom</th>
                  <th scope=”col”>Email</th>
                  <th scope=”col”>Date d’inscription</th>
              </tr>
              <tr v-for=”user in users” v-bind:key=”user.id” style=”margin-bottom: 5px;”>
                  <th scope=”row”>{{ user.id }}</th>
                  <td>{{ user.name }}</td>
                  <td>{{ user.email }}</td>
                  <td>{{ user.created_at}}</td>        </tr>
          </table></div>
      </template><script>
        export default {
          data() {
            return {
              has_error: false,
              users: null
            }
          },   mounted() {
            this.getUsers()
          },   methods: {
            getUsers() {
              this.$http({
                url: `users`,
                method: ‘GET’
              })
                  .then((res) => {
                    this.users = res.data.users
                  }, () => {
                    this.has_error = true
                  })
            }
          }
        }
      </script>

      Note: we use the ‘$http()’ method from Vue-Auth that will automatically add the token in the request headers.

      Then, in the ‘resources/js/pages/admin/Dashboard.vue’ file, replace the content by the following code:

      <template>
          <div class=”container”>
              <div class=”card card-default”>
                  <div class=”card-header”>Admin Dashboard</div>            <div class=”card-body”>
                      Bienvenue sur votre dashboard administrateur
                  </div>   </div>
              <div class=”card card-default”>
                  <div class=”card-header”>Liste des utilisateurs</div>            <div class=”card-body”>
                      <userList></userList>
                  </div>
              </div>
          </div>
      </template><script>
        import userList from ‘../../components/user-list.vue’
        export default {
          mounted() {
            //
          },
          components: {
            userList
          }
        }
      </script>

      Thus, when an admin connects to its dashboard, he can now see the users list.

       

      Application screenshot

      Note: if you try the same in the user’s dashboard, you should have an error message. Indeed, the endpoint ‘/api/users’ is protected an only accessible for administrators!

      Conclusion

      This tutorial is finished, I hope it will help you to st up beatiful applications with Laravel and Vue 🙂

      This is my first article on Medium (and the first time I translate a text in english), so do not hesitate to make feedbacks, whether at level of writing, translation or code quality.

      I have been inspired by several tutorials dealing with this subject, but not going far enough in my opinion in the protection of routes and in the management of roles.

      If you think I did not explain a part correctly, that I made mistakes in the way of proceeding, or if you don’t get the expected result after following this tutorial, let me know and i’ll do my best to answer you!