Modern applications usually displays only what is visible to the user based on their role. For example, a guest user may read stories but can’t write comments on Medium. Or another example, an authorized user can write and remove drafts. But that user is not allowed to see or remove somebody’s else drafts. And that’s pretty reasonable, isn’t it? :)

It all sounds cool but sometimes managing such accessibility may become a nightmare. You probably have written or seen code like this before:

Later, this code is spread over the application and becomes a big problem when you need to add a new role in the app or change permissions of existing role. Eventually you need to change some of *ngIf checks or in the worst case, change them all.

In this story, I want to share an alternative way to implement permission management by using a neat library which is called CASL.It makes managing user permissions much simpler, and allows to rewrite the previous example to:

Demo application

Angular Todo App with CASL

To illustrate how to use CASL, I decided to use well known Todo appication with small additions:

you can assign tasks to users (“me” represents current user)

you can switch between roles

There are two roles in application:

“member” (i.e., regular user). Can read all Todos and CRUD (i.e., create, read, update, delete) only on assigned to him

“admin”. Can CRUD any Todo

This logic is defined by using AbilityBuilder class which allows to define user permissions by using declarative function calls:

In order to understand this function, we need to dive a bit deeper into CASL details. To do this, let’s go through each line of defineAbilitiesFor(role) function.

AbilityBuilder.extract() creates an instance of AbilityBuilder and extracts rules array and its can , cannot methods. With help of can (or cannot which is not used here), it’s possible to define user permissions. can (and cannot ) both accepts 4 arguments, 2 of which are required: action name and subject name.

Action name represents a user’s action in application (in most cases it will be standard CRUD) and subject name represents model name which this rule is defined for.

Let’s take a look a the code again.

If role argument of defineAbilitiesFor(role) equals admin , then user can do anything in the system (i.e., super admin rights). This is specified by can('manage', 'all') statement. As I said before the first argument is an action name, manage is a special keyword which represents any action in the system. And the second argument is a subject name, in this case it’s all , which is a reserved alias for any subject name. So, if user has role admin he can perform any action on any model.

In case user is not an admin, he can read any model (this is specified by can('read', 'all') ) and can do any action on instances of Todo model where model’s assignee property equals to me (i.e., can('manage', 'Todo', { assignee: 'me' }) ).

As you can see, the last permission declaration has 3 arguments. The third arguments represents conditions object. In this case, it clarifies which instances of Todo user is allowed to manage.

To understand it clearly, let’s look at this example:

Here I created a separate class Todo which represents a task to be done. This class accepts an object which must contain 2 fields: task’s title and who should work on this task. Later, I create 2 instances of Todo class: 1 that represent task assigned to me (remember conditions object?) and another one which is assigned to John . I also create an Ability instance with permissions for user with member role.

Finally, I check permissions on these 2 todos. As you can see ability.can("read", myTodo) and ability.can("close", myTodo) returns true , this is because early we defined that users who are not admins can do anything with tasks assigned to them (i.e., can('manage', 'Todo', { assignee: 'me' }) ). Due to exactly the same reason, ability.can("close", johnTodo) returns false because we can only read his todo not close (i.e., can('read', 'all') ).

Do you see how closely explanation in console.log reflects the actual check? They are almost identical (except that first one just a string and the last one do a complex permission check :)):

console.log(

"can read my todo?",

ability.can("read", myTodo)

)

Of course, now some of you may have questions like these:

What other restrictions can be enforced by conditions object? You can learn about this in documentation here. Shall I always define a class for all my models on UI? The short answer is NO but if you don’t use classes (and I think majority of devs don’t use them on frontend), you need to let CASL know how to detect subject name based on the object instance passed in ability.can(action, subject) . You can learn about this here.

CASL and Angular

CASL is shipped together with complementary package for Angular 2+. So, you can quickly add it into your Angular app.

Let’s start from installation

npm i @casl/ability @casl/angular

# or

yarn add @casl/ability @casl/angular

Later, include AbilityModule in your AppModule (use AbilityModule.forRoot() if you include it in root AppModule ):

@casl/angular provides the empty instance of Ability by default. Here I specified own provider which defines Ability based on provided rules (i.e., permissions). Alternatevely it’s possible to update rules of provided by default instance

Afterwards, you can inject Ability instance in your component to check permissions or update them:

Alternatevely you can use can pipe in templates for simple checks (more details here). So, you can write code like this in Angular’s templates:

With that, we have a really nice way to manage permissions in Angular app.

The full example of the Angular based app with CASL can be found on github or codesandbox: