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.
Update: check CASL 2.0 to see new features!
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:
- Managing permissions in Expressjs app
- Easy API Authorization with CASL and Feathers
- Managing permissions with CASL in Vue app
- Managing permissions with CASL in Aurelia app
- Managing permissions with CASL in Angular app
- Managing user permissions in React app
- Permissions sharing between UI and API
If you liked this article, please recommend CASL!