What is CASL or how can you build a castle around your application? 🏰

Nowadays almost every application provides different functionality for different groups of users (e.g., admin, member, subscriber, etc). These groups usually are called “roles”.

In my experience, permission logic is built around roles (e.g., if user has this role, then he can do this) and end up with massive hard to support system.

That’s why I wrote CASL (pronounced /ˈkæsəl/, like castle)— an authorization JavaScript library which forces you to think about user abilities in the system. Afterwards these abilities can be split into sets which eventually can be mapped to roles (e.g., if user has this ability, then he can do this). For example, in a blog application user can create, edit, delete, view posts and comments. Lets split these abilities between 2 groups of users: anonymous users (those who has not logged in) and writers (those who has logged in).

Anonymous users can only read posts and comments. Writers can do the same and additionally they can manage their own posts and comments (“manage” means create, read, update and delete). With CASL this may look like this:

Thus we can define what user can do based on whatever logic we want, including roles separation. For example, we can allow users to moderate someone else comments or posts based on their reputation, allow to view some content only for people who confirmed that they are 18 years old, etc. With CASL you can define all these permission logic in one place!

Moreover it allows to use basic MongoDB query operators to define your abilities. See Defining Abilities for details.

Checking Abilities

There are 3 methods on Ability instance which allows to check user’s abilities:

ability.can('update', 'Post')
ability.cannot('update', 'Post')
ability.throwUnlessCan('update', 'Post')

The first method returns false, the second returns true and the third throws ForbiddenError for anonymous users. As the second argument these methods may accept a class instance. They determine object’s type based on constructor.name or custom logic which you can provide via subjectName option of Ability constructor:

const post = new Post({ title: 'What is CASL?' })
ability.can('read', post)

In this case, can('read', post) returns true because in abilities we defined that every user can read all posts. Lets check a case when user tries to update someone else post (I will refer to another author id as anotherId and currently logged in user’s id as myId):

const post = new Post({ title: 'What is CASL?', authorId: 'anotherId' })
ability.can('update', post)

In this case, can('update', post) returns false because in abilities we defined that user can update only his own posts. Obviously for our own post it returns true:

const post = new Post({ title: 'What is CASL?', authorId: 'myId' })
ability.can('update', post)

See Checking Abilities in the official documentation for more details.

Database integration

CASL provides functions which allows to convert ability rules into database query. Currently it supports only MongoDB but this can be extended to any query language you need.

You can convert abilities to MongoDB query using toMongoQuery function and rulesFor method of Ability instance

import { toMongoQuery } from 'casl'
const query = toMongoQuery(ability.rulesFor('read', 'Post'))

In this case, query variable is an empty object because user is able to read all posts. Lets check output for update operation:

const query = toMongoQuery(ability.rulesFor('update', 'Post'))
// { $or: [{ authorId: 'myId' }] }

Now it contains query which should return only posts which were created by me. All regular rules are logically OR-ed that’s why you see $or operator in resulting query. See Combining Abilities for details.

Also CASL provides mongoose plugin which adds accessibleBy method to models. This method internally calls toMongoQuery function and pass results into find method of mongoose Query.

By default, accessibleBy will look for abilities for read action but you can pass any other action as the second parameter to this method. See Database Integration for other details.

One more thing

CASL is written in pure ES6 thus can be used in any JavaScript environment. That means you can use the same authorization library on UI and API sides. And UI can request user’s abilities from API in order to show or hide some specific functionality.

If you are interesting in integration examples with popular frameworks, please check the next articles:

If you liked this article, please consider recommending it. 👏