Vue ACL with CASL

Recently I had a chance to work with a Vuejs2 based application. It’s amazing how simple and powerful Vue is.

Nowadays almost every app is used by multiple users, that means almost every application needs to have an access control layer to provide different functionality to different users.

So, in this post I’m going to show how CASL can help your Vue app in this journey. If this is the first time you hear about CASL, please read “What is CASL?”.

Abilities in application

This story is about CASL, so I won’t go through Vue CLI or Vue basic concepts, all that was well documented by other guys. That’s why I decided to create the simplest Todo application with possibility to set assignee for newly created todo item. This allows us to define permissions based on assignee.

Overall when you open an app you will see something like this

As I mentioned before, each task must have an assignee, if you want to assign a task to yourself, just pick “me” in dropdown options.

By default, users will be able to manage only tasks which were assigned to them. This is defined with help of AbilityBuilder.define method and subjectName option which allows to customize type detection based on whatever logic you want:

In order to detect object type, I’ll add additional property __type to all Todo items, so that CASL can understand which rules need to be applied to that object.

This is how I’d like to see CASL in Vue:

  • all Vue components have $can method;
  • in places where it’s required to hide UI element I’ll use v-if directive together with $can method;
  • it should be possible to use $can method with any directive, component or filter.

The main advantage of exposing abilities via $can method and not create a separate directive (e.g., v-can ) is a possibility to combine permissions logic with any other boolean checks and pass it as a parameter of directives and components.

In that Todo application I added $can checks in the next places:

  • todo creation input will be shown only if user has an ability to create tasks;
  • mark as complete checkbox will be shown only if user has an ability to update that task;
  • delete button will be shown only if user has an ability to delete a task.

Vue plugin

At first I thought that integration would be extremely easy and I didn’t even need to write an article because any js developer is able to define a custom property on Vue.prototype

Vue.prototype.$can = ability.can.bind(ability)

The solution above works perfectly until you try to update abilities with some asynchronous function (like requesting a session or permissions).

The issue is that Vue can track changes only in reactive objects (i.e., these objects are created by Vue internally). Instance of Ability are definitely is not a reactive object :) So, I needed to find a way to update the whole Vue application when somebody calls ability.update.

Update: now there is a separate package which allows to integrate CASL seamlessly into any Vue application. See updated repository for an example

Fortunately, there is $forceUpdate method which forces component to re-render its HTML. But again… it re-renders only the component which calls it but doesn’t touch child components. So, what I needed to do is to call $forceUpdate on every component in application when ability.update is called.

Vue.mixin allows to add lifecycle hooks/methods/properties/whatever to all Vue components. I need to add logic in 2 hooks, beforeCreate to create subscription for permissions update and beforeDestroy for subscription cleanup. Also, I need to monkey patch update method of an Ability instance, to trigger event when permissions are changed. And all together:

To apply this plugin, we need to call Vue.use and pass an instance of Ability class as parameter:

The full example of Vue authorization with help of CASL can be found on Github.

To play around with your permissions logic, just open Dev Tools and type:

  • ability.update([]) to reset all permissions (i.e., readonly mode);
  • ability.update([{ subject: 'all', actions: 'manage' }]) to get full access to manage everything;
  • ability.rules to get a list of current abilities;
  • for more information about possible options and how to configure ability please read in official documentation and/or ask questions in gitter chat.

That’s it 🙂