In the first part of this tutorial, we set-up our Laravel 7 API backend with 4 APIs and guarded them with permissions and roles. To clarify, we set up three types of roles, namely, Super Admin, User Manager, and Role Manager. These roles are made up of one or more permissions, which in turn, grant the User a set of permissions to access the APIs exposed by the Laravel 7 application. Therefore, if you have not gone through the first part already, I highly suggest you do so. Now, let’s begin implementing access control using permissions and roles in Vue!

Now that our backend is set up with access control, it is time to implement the same access control in our Vue SPA front-end. Note that our Vue application is a standalone application built with vue-cli, intended to be running on a different server than our API.

Vue SPA – Roles and Permissions Overview

We build a Vue.js Single Page Application(SPA) that communicates with our existing Laravel 7 API backend. The SPA has functionalities such as login, dashboard, roles, and permissions. However, the exiting part of our application is that the dashboard is automatically populated. The dashboard shows only those functionalities to which the User has access from the backend!

Setting up Your Environment for Vue.Js

Before we can begin setting up our Vue application, we need to take care of a few dependencies.

Install Node.js

Node.js is a javascript run-time environment, and it executes Javascript code without the need for a browser. If you have not done it already, then you should go ahead and install Node.js now.

  • Install Node.js on Windows.
  • Node.js installation for macOS.
  • Install Node.js on Linux.

Once you have installed it, you can verify the installation. Firstly, restart your machine/terminal and run:

npm -v

The output should be the current version of your Node.js installation. We use the npm command to install/update/remove the dependencies in our Vue.js application.

Install Vue-CLI

vue-cli is a command-line tool provided by Vue.js to quickly create and bootstrap Vue Single Page Applications.

We install vue-cli in our system using npm so that it is globally available in all our terminals.

npm install -g @vue/cli

To verify that we successfully installed vue-cli, we run the following command to make sure that it shows information related to vue-cli

vue --version

Create a New Vue Project

From the command line, navigate to the directory where you want to create your new project and run the following command:

vue create role-permissions-demo

Select the default preset (babel, eslint) and hit Enter.

Note:

Babel is a transpiler. It converts the new javascript (ES6) code into vanilla javascript that any browser can understand. And Eslint is an ES6 linter used to maintain code coherency and better coding practices across your project,

To verify if everything works correctly, run the following command:

npm run serve

Now go to http://localhost:8080 in your browser and you should see the default Vue welcome page! We can now get started with implementing our access control logic.

Setting Up Application Routes

Unlike Laravel, Vue is just a UI framework and comes with no routing functionality by default. However, you can easily install it by pulling in the famous vue-router package.

  • Install vue-router:
npm install vue-router
  • Let’s define our application routers by creating a new file routes.js and adding routes that will be handled by the vue-router. For the sake of brevity, I only define the bare minimum routes here. However, apart from these, you can also add the routes specific to your application here,
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

const router = new Router({
  mode: 'history',
  base: 'http://localhost:8080',
  routes: [{
    path: '',
    component: () =>
      import ('./layouts/main/Main.vue'),
    children: [{
        path: '/',
        redirect: '/login',
      }, {
        path: '/home',
        name: 'home',
        component: () =>
          import ('./views/Home.vue'),
        meta: {
          authRequired: 'true',
        }
      },

      {
        path: '/login',
        name: 'login',
        props: true,
        component: () =>
          import ('@/views/Login.vue'),
        meta: {
          authRequired: 'false',
        },
      },

      {
        path: '/permissions',
        name: 'permissions',
        props: true,
        component: () =>
          import ('@/views/Permissions.vue'),
        meta: {
          authRequired: 'true',
        },
      },

      {
        path: '/roles',
        name: 'roles',
        props: true,
        component: () =>
          import ('@/views/Roles.vue'),
        meta: {
          authRequired: 'true',
        },
      },

      {
        path: '/users',
        name: 'users',
        props: true,
        component: () =>
          import ('@/views/Users.vue'),
        meta: {
          authRequired: 'true',
        },
      },

     {
        path: '/not-authorized',
        name: 'not-authorized',
        props: true,
        component: () =>
          import ('@/views/NotAuthorized.vue'),
        meta: {
          authRequired: 'false',
        },
      },

    ],
  })
});
  • Notice how we also set authRequired meta attribute to each of our routes. This tells us whether the user needs to be authenticated to access a page.
  • Let’s import the router.js file we created above to our Vue application by going into main.js and adding the following code.
import Vue from 'vue'
import routes from './routes'

new Vue({
    router,
    render: h => h(App)
}).$mount('#app');

Log In the User and Get Permissions

Now that we have set up our routes, we need to define our Login.vue and set up the logic so that a user can log in. Here is an example:

<template>
  <div>
    <input name="email" label="Login ID" v-model="email" />
    <span class="text-danger text-sm">{{ errors.first('email') }}</span>
    <inpu type="password" name="password" label="Password" v-model="password" />
    <span class="text-danger text-sm">{{ errors.first('password') }}</span>
    <div class="flex flex-wrap justify-between my-10">
      <button :disabled="!validateForm" @click="login">Login</button>
    </div>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  data() {
    return {
      email: '',
      password: '',
    }
  },
  computed: {
    //check if both password and email have been set for enabling login button
    validateForm() {
      return this.email != '' && this.password != '';
    },
  },
  methods: {

    login() {

      const payload = {
        email: this.email,
        password: this.password
      }

      //post credentials and get access token from laravel backend
      axios.post('http://localhost:8000/login', payload)
        .then(() => {

          //we store the access token in localstorage, so that we can use it again.
          localStorage.setItem("accessToken", response.data.access_token);

          //we also store the user permissions in localstore.
          //This is needed to implement access control.
          localStorage.setItem("userPermissions", response.data.permissions);

          //after storing token, send user to home page.
          this.$router.push('/home');

        })
        .catch(error => {
          console.log(error)
        })
    }
  }
}
</script>

The Key takeaway from the code above is:

  • We log in the user by submitting a POST request to /login route.
  • The Laravel server returns the access_token along with the user’s permissions list.
{
   "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiZmJlYzg0ZjhjMjE2YzhhY
   "permissions": ["Create Roles", "Create Users",...]
}
  • We then store this access_token and permissions in users’ local storage so as to send requests to the server seamlessly and also implement access control.

Access Control Using Permissions and Roles in Vue

In the previous step, we successfully fetched the User’s permissions and stored them in the browser’s storage. Now we can implement access control in our frontend based on these permissions. We will see how to implement access control for both our routes and UI elements.

Access Control For Routes

We implement access control for our application’s routes by checking certain conditions before every route change:

  1. Does the route require the user to be logged in? (authRequired)
  2. If a route requires authentication, does the user also has the permission to view the route?

We let the user access the route if both of the above conditions hold true for them.

Now, let’s define this logic in routes.js. The vue-router package provides a handy routes.beforeEach() method where we define our access control logic. beforeEach() is a global guard method that gets invoked before every route change.

router.beforeEach((to, from, next) => {

  //A Logged-in user can't go to login page again
  if (to.name === 'login' && localStorage.getItem("accessToken")) {
    router.push({
      name: 'home'
    })

    //the route requires authentication
  } else if (to.meta.authRequired) {

    if (!localStorage.getItem("accessToken")) {

      //user not logged in, send them to login page
      router.push({
        name: 'login',
        query: {
          to: to.name
        }
      })

    } else {
      if (!this.hasAccess(to.name)) {
        router.push({
          name: 'page-not-authorized'
        })
      }
    }
  }

  return next()
});


hasAccess(name) {
  permissions = localStorage.getItem("permissions")

  switch (name) {

    case "home":
      return true;

    case "users":
      return permissions.includes("View All Users")

    case "permissions":
      return permissions.includes("View All Permissions")

    case "roles":
      return permissions.includes("View All Roles")

    default:
      return false;
  }
}

That’s it! Permissions now guard the access to our routes. Therefore, even if a user manually enters a route in the browser’s URL bar, if they do not have access to it, they are redirected to the /not-authorized page!

Access Control for UI Elements:

Now that we have access control for our routes, we have another essential part to cover. What if we want to display or hide certain elements in our view depending upon the user’s permissions? For example, an edit/delete button is only visible to those user’s that have permission to access that specific API.

We can do this easily by defining a computed property that checks if a user has specific permission or not. We can then use this property as a condition for the v-if directive we place on our UI elements. v-if allows us to render an HTML element in our Vue application conditionally.

Here’s an example:

<template>
  <div>
    <div v-if="canCreateUsers">
      <button :disabled="!validateForm" @click="createUser">Create User</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      permissions: localStorage.getItem("permissions"),
    }
  },
  computed: {
    canCreateUsers() {
      return = this.permissions.includes("View All Users")
    },
  },
  methods: {
    createUser() {
      //user creation logic
    }
  }
}
</script>

Note: I understand that we’re dealing with javascript here that runs on user’s browser. A skilled user might be able to get into the DOM and change the permissions variable or even disable the routing logic! However, this would not affect the application as we have access control implemented in our backend API as well. Therefore, even if a user manages to bypass the front-end security, our backend holds good against such malicious attacks.

That’s it, and we have successfully implemented Access Control Using Permissions and Roles in Vue! If you want to see a real-world project where I implement this on a production-grade application, you can check out this video:

If you have any questions or doubts, feel free to ask them the comment section below. You can also check out our collection of Laravel and Vue related articles on this website.