Role Based Authorization for your Vue.js and Nuxt.js Applications Using vue-kindergarten

I really enjoy building applications using Vue.js! It is a kind of a framework I have been waiting for since I’ve stopped using Angular 1. I have tried many other frameworks, but I didn’t really get excited about any of them. Vue.js is simpler to learn, requires less boilerplate code, and applications written in Vue.js feel more readable for me than applications written using React/Redux combo (looking at the official example of todo app). One of things I like the most in Vue is that one can easily extend the functionality using plugins. This article is about one such plugin I have created — it’s called vue-kindergarten.

What is Vue-Kindergarten?

Vue-kindergarten is a plugin for Vue.js and Nuxt.js applications that simplifies an implementation of role based authorization. It is basically an integration of kindergarten library into Vue ecosystem. The main advantages are:

  • Provides an easy way to define new authorization rules
  • Groups authorization rules by a certain business domain
  • Protects routes and components using the same pattern
  • Is framework independent and works on the server as well
  • Provides an easy way to create a new behavior when user is trying to access a restricted resource
  • Lightens store and components by moving authorization rules to modules

Terms Used in Vue-Kindergarten

Vue-Kindergarten is based on sandbox pattern. If you’ve tried YUI Library, then you are probably familiar with it. In vue-kindergarten your components act as sandbox and their functionality can be extended using modules.

Perimeter

Perimeter is a module that represents business domain in you application. Perimeter can define method that should be exposed and some rules that must be followed.

Sandbox

Modules (perimeters) are plugged into sandbox and all exposed methods will be accessible through it. Sandbox is governed by a governess and she is the one who makes sure that all rules are followed in order to prevent any kind of troubles.

Governess

The governess is guarding your sandbox. She makes sure that child doesn’t do any unauthorized activities and she can deal with troubles your way! Governess defines the behavior of your application when user is doing illegal stuff.

Child

Child in vue-kindergarten represents the current user of your application.

What is Role Based Authorization?

In computer systems security, role-based access control is an approach to restricting system access to authorized users.

Our job on client side is to hide certain elements if user is not allowed to see them and to prevent user to navigate to any restricted page. We are not really restricting user to access our resources though — that should be done on the server! Never trust Front-End, because hackers can easily bypass your restrictions.

Let’s Do Some Coding!

Let’s build a simple Vue+Vuex+Vue-Router application and integrate vue-kindergarten in order to protect our components and our routes.

We will be using vue-cli to create a boilerplate code:

% yarn global add vue-cli
% vue init example-app # install vue-router
% cd example-app
% cd example-app
% yarn add vuex
% yarn
% yarn dev

Time to create some routes:

// src/router/index.js
import Vue from 'vue';
import Router from 'vue-router';
import Hello from '@/components/Hello';
import Bye from '@/components/Bye';
import Secret from '@/components/Secret';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
name: 'hello',
component: Hello,
},
{
path: '/bye',
name: 'bye',
component: Bye,
},
{
path: '/secret',
name: 'secret',
component: Secret,
},
],
});

Create our page components:

<!-- src/components/Hello.vue -->
<template>
<div>
<h1>Hello!</h1>
<router-link to="/bye">Bye</router-link>
<span>|</span>
<router-link to="/secret">Secret</router-link>
    <p>
<input
type="radio"
name="role"
value="admin"
v-model="role"
@change="onRoleChange" />
<label>Admin</label>
</p>
<p>
<input
type="radio"
name="role"
value="user"
v-model="role"
@change="onRoleChange" />
<label>Regular User</label>
</p>
</div>
</template>
<script>
import { mapMutations, mapState } from 'vuex';
  export default {
computed: {
...mapState([
'user',
]),
},
    data() {
return {
role: 'admin',
};
},
    mounted() {
this.role = this.user.info.role;
},
    methods: {
...mapMutations([
'changeRole',
]),
      onRoleChange() {
this.changeRole(this.role);
},
},
};
</script>
<!-- src/components/Bye.vue -->
<template>
<div>
<h1>Bye!</h1>
<router-link to="/">Hello</router-link>
<span>|</span>
<router-link to="/secret">Secret</router-link>
    <p>This paragraph is secret!</p>
</div>
</template>
<!-- src/components/Secret.vue -->
<template>
<div>
<h1>You have found it!</h1>
<router-link to="/">Hello</router-link>
<span>|</span>
<router-link to="/bye">Bye</router-link>
</div>
</template>

And Vuex store:

// src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
user: {
info: {
name: 'Bob',
role: 'admin',
},
},
},
  mutations: {
changeRole(state, newRole) {
state.user.info.role = newRole;
},
},
});

Reference store in the main file:

// src/main.js
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store';
Vue.config.productionTip = false;
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App },
});

That’s it - we have now simple application with some routes components and a store. The radio-boxes are changing the role of the user in the store. Our goal now is to hide the paragraph inside of Bye.vue component when user is not an admin. Let’s add vue-kindergarten first:

yarn add vue-kindergarten

The authorization rules are defined in perimeters. Each perimeter represents a certain business domain. We will create 2 perimeters in our case: one for the “secret” page to restrict the access to admins only and the second perimeter for the “bye” page to hide that paragraph. It’s actually really up to you how many perimeters you create. If you are building a blog page then you could have one perimeter for articles, second for comments and third for tags, or you could also keep all the rules in one single perimeter — the choice is yours!

Since “bye” perimeter and “secret” perimeter are both checking if the current user is admin — we can create a parent perimeter that will implement helper method for that purpose:

// src/perimeters/BasePerimeter.js
import { Perimeter } from 'vue-kindergarten';
export default class BasePerimeter extends Perimeter {
isAdmin() {
return this.child && this.child.role === 'admin';
}
}

Now we can create the perimeter for bye page:

// src/perimeters/byePerimeter.js
import BasePerimeter from './BasePerimeter';
export default new BasePerimeter({
purpose: 'bye',
  govern: {    
'can route': () => true,
    'can viewParagraph': function () {
return this.isAdmin();
},
},
});

It’s super simple to restrict the paragraph for admins only. First thing we have to do is to add vue-kindergarten plugin to Vue:

// src/main.js
import Vue from 'vue';
import VueKindergarten from 'vue-kindergarten';
import App from './App';
import router from './router';
import store from './store';
import child from './child';
Vue.config.productionTip = false;
Vue.use(VueKindergarten, {
child,
});
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App },
});

As I mentioned earlier the child represents the current user of our application. We need to provide a method to retrieve the current user. In our case the current user is stored in the store, so the getter will look like this:

// src/child.js
export default store => (store ? store.state.user.info : null);

Now let’s actually hide the paragraph:

// src/components/Bye.vue
<template>
<div>
<h1>Bye!</h1>
<router-link to="/">Hello</router-link>
<span>|</span>
<router-link to="/secret">Secret</router-link>
    <p v-show="$isAllowed('viewParagraph')">This paragraph is secret!</p>
</div>
</template>
<script>
import byePerimeter from '../perimeters/byePerimeter';
  export default {
perimeters: [
byePerimeter,
],
};
</script>

That’s it. The paragraph will be visible if the “Admin” radio box is selected and it will be hidden when you select the “Regular User” radio box. If your component loads multiple perimeters and the viewParagraph method is defined in some of them then you can do following:

<p v-show="$bye.isAllowed('viewParagraph')">This paragraph is secret!</p>

$bye represents the purpose of the perimeter.

The next step is to restrict the regular user to access the secret page. Let’s create a secret perimeter first:

// src/perimeters/secretPerimeter.js
import BasePerimeter from './BasePerimeter';
export default new BasePerimeter({
purpose: 'secret',
  govern: {
'can route': function () {
return this.isAdmin();
},
},
});

And now we can protect our route:

// src/router/index.js
import Vue from 'vue';
import Router from 'vue-router';
import { createSandbox } from 'vue-kindergarten';
import Hello from '@/components/Hello';
import Bye from '@/components/Bye';
import Secret from '@/components/Secret';
import * as perimeters from '../perimeters';
import child from '../child';
import store from '../store';
Vue.use(Router);
const router = new Router({
routes: [
{
path: '/',
name: 'hello',
component: Hello,
},
{
path: '/bye',
name: 'bye',
component: Bye,
},
{
path: '/secret',
name: 'secret',
component: Secret,
},
],
});
router.beforeEach((to, from, next) => {
const perimeter = perimeters[`${to.name}Perimeter`];
  if (perimeter) {
const sandbox = createSandbox(child(store), {
perimeters: [
perimeter,
],
});
    if (!sandbox.isAllowed('route')) {
return next('/');
}
}
  return next();
});
export default router;

This code works, however as I mentioned earlier the governess is responsible for the default behavior when user is trying to access protected resource. So let’s create our first governess:

// src/governesses/RouteGoverness.js
import { HeadGoverness } from 'vue-kindergarten';
export default class RouteGoverness extends HeadGoverness {
constructor({ from, to, next }) {
super();
    this.next = next;
this.from = from;
this.to = to;
}
  guard(action) {
this.next(super.isAllowed(action) ? undefined : '/');
}
}

and we can now use her in the our beforeEach callback:

import Vue from 'vue';
import Router from 'vue-router';
import { createSandbox } from 'vue-kindergarten';
import Hello from '@/components/Hello';
import Bye from '@/components/Bye';
import Secret from '@/components/Secret';
import * as perimeters from '../perimeters';
import RouteGoverness from '../governesses/RouteGoverness';
import child from '../child';
import store from '../store';
Vue.use(Router);
const router = new Router({
routes: [
{
path: '/',
name: 'hello',
component: Hello,
},
{
path: '/bye',
name: 'bye',
component: Bye,
},
{
path: '/secret',
name: 'secret',
component: Secret,
},
],
});
router.beforeEach((to, from, next) => {
const perimeter = perimeters[`${to.name}Perimeter`];
  if (perimeter) {
const sandbox = createSandbox(child(store), {
perimeters: [
perimeter,
],
      governess: new RouteGoverness({ from, to, next }),
});
    return sandbox.guard('route');
}
  return next();
});
export default router;

Notice that we are calling the guard method on the sandbox which will call the guard method of our governess. And here is the result:

Simple right? Vue-kindergarten provides an easy way to lighten your components and your store by moving the authorization rules to perimeters.