Introduction

@casl/ability NPM version Support

What is CASL?

CASL (pronounced /ˈkæsəl/, like castle) is an isomorphic authorization JavaScript library which restricts what resources a given client is allowed to access. It's designed to be incrementally adoptable and can easily scale between a simple claim based and fully featured subject and attribute based authorization. It makes it easy to manage and share permissions across UI components, API services, and database queries.

CASL implements Attribute Based Access Control

Getting Started

The easiest way to try out CASL.js is using the Hello World example. Feel free to open it in another tab and follow along as we go through some basic examples. Alternatively you can create Nodejs project and install @casl/ability as a dependency.

The provides more options of installing CASL.

Basics

CASL operates on the abilities level, that is what a user can actually do in the application. An ability itself depends on the 4 parameters (last 3 are optional):

  1. User Action
    Describes what user can actually do in the app. User action is a word (usually a verb) which depends on the business logic (e.g., prolong, read). Very often it will be a list of words from CRUD - create, read, update and delete.
  2. Subject
    The subject or subject type which you want to check user action on. Usually this is a business (or domain) entity (e.g., Subscription, Article, User). The relation between subject and subject type is the same as relation between an object instance and its class.
  3. Fields
    Can be used to restrict user action only to matched subject's fields (e.g., to allow moderator to update status field of an Article and disallow to update description or title)
  4. Conditions
    Criteria which restricts user action only to matched subjects. This is useful when you need to give a permission on specific subjects (e.g., to allow user to manage own Article)

At the core of CASL is a system that enables us to declaratively define and check user permissions using clear javascript syntax:

import { defineAbility } from '@casl/ability';

export default defineAbility((can, cannot) => {
  can('manage', 'all');
  cannot('delete', 'User');
});

CASL has sophisticated support for TypeScript but in this guide we will use JavaScript for the purpose of ease. See for details

In the example above, we have just defined a MongoAbility instance which permits doing anything in the app except for deleting users. As you probably guessed, can and cannot accept the same arguments but have different meanings: can permits an action on the specified subject and cannot forbids it. Both may accept up to 4 arguments (in exactly the same order as listed in concepts section). In this case, manage and delete are user actions and all and User are subjects.

manage and all are special keywords in CASL. manage represents any action and all represents any subject.

Now let's try to check permissions

import ability from './defineAbility.js';

ability.can('read', 'Post') // true
ability.can('read', 'User') // true
ability.can('update', 'User') // true
ability.can('delete', 'User') // false
ability.cannot('delete', 'User') // true

In the example above, the MongoAbility instance allows us to check permissions in a pretty readable way. By the way, all these examples demonstrate checking permissions based on a subject type (i.e. an object type or class), but CASL really shines when you need to restrict objects based on their attributes (i.e. properties).

Conditions

The most common requirement for mid-sized apps is the ability to limit users so that they can perform actions only on their own resources. CASL allows us to do this by passing an object of conditions as the 3rd argument to can and cannot methods on the definition step.

Before diving into the details, let's first consider requirements for the permissions of a blog website. In such a blog, a user

  • can read any Article
  • can update own Article's
  • can create a Comment for any Article
  • can update own Comment

Let's translate this to CASL:

import { defineAbility } from '@casl/ability';

export default (user) => defineAbility((can) => {
  can('read', 'Article');

  if (user.isLoggedIn) {
    can('update', 'Article', { authorId: user.id });
    can('create', 'Comment');
    can('update', 'Comment', { authorId: user.id });
  }
});

Do you see how real business requirements are easily translated to code? Now let's check them!

import defineAbilityFor from './defineAbility';

const user = { id: 1 };
const ability = defineAbilityFor(user);
const article = /* intentionally not defined */;

ability.can('read', article);

As you see, you can do checks on the subject the same way you do it on the subject's type! But what does the article variable hold inside? How does CASL know the subject type of the object referenced by this variable?

Do you remember that a subject and its type belong to each other in the same way as an object instance and its class? CASL remembers this as well and retrieves article.constructor.name as its subject type.

Classes are natural in backend but not always makes sense in frontend development. CASL supports other ways to detect subject type, see for details.

Let's get back to our example and define classes for Article and Comment entities:

class Entity {
  constructor(attrs) {
    Object.assign(this, attrs);
  }
}

export class Article extends Entity {}

And this is how the example with missed article variable looks eventually:

import defineAbilityFor from './defineAbility';
import { Article } from './entities';

const user = { id: 1 };
const ability = defineAbilityFor(user);
const article = new Article();

ability.can('read', article); // user can read any article

And few more examples just to get familiar with:

import defineAbilityFor from './defineAbility';
import { Article } from './entities';

const user = { id: 1, isLoggedIn: true };
const ownArticle = new Article({ authorId: user.id });
const anotherArticle = new Article({ authorId: 2 });
const ability = defineAbilityFor(user);

ability.can('read', 'Article') // true
ability.can('update', 'Article') // true
ability.can('update', ownArticle) // true
ability.can('update', anotherArticle) // false, we can't update articles which were not written by us

Despite the fact that can and cannot functions in defineAbility callback are similar to can and cannot methods of MongoAbility class, they have completely different purposes and accept different arguments. See if it confuses you.

Pay attention that conditions object contains the same keys as the entity we want to check. This is how CASL matches entities by conditions. In our case, it just checks that authorId in Article instance equals to authorId in conditions object. Conditions may have several fields, in that case all fields should match (AND logic).

But conditions are not restricted to simple equality checks! Thanks to ucast MongoAbility instances can match objects using MongoDB query language.

If you are not familiar with MongoDB query language, see for details

You can define the same pair of action and subject with different conditions multiple times. For example, let's allow our blog users to share drafts and publish articles:

import { defineAbility } from '@casl/ability';

export default function defineAbilityFor(user) {
  return defineAbility((can) => {
    can('read', 'Article', { published: true });
    can('read', 'Article', { published: false, sharedWith: user.id });
  });
}

In such case, the pair of action/subject rules are combined by logical OR. More formally, this can be translated as "users can read Article if it's published OR users can read Article if it's not published AND shared with them".

But that's not all, if you need more granular permission checks, you can define them on subject's attributes (i.e., fields)!

Fields

Sometimes you may need to restrict which fields a user can access. For example, let's allow only moderators to publish Article:

import { defineAbility } from '@casl/ability';

export default (user) => defineAbility((can) => {
  can('read', 'Article');
  can('update', 'Article', ['title', 'description'], { authorId: user.id })

  if (user.isModerator) {
    can('update', 'Article', ['published'])
  }
});

Here we defined that any user can update title and description fields of their own Articles and only moderators can update published field.

If fields are not specified, a user is allowed to access any field.

To check permissions use the same can and cannot methods of MongoAbility instance:

import defineAbilityFor from './defineAbility';
import { Article } from './entities';

const moderator = { id: 2, isModerator: true };
const ownArticle = new Article({ authorId: moderator.id });
const foreignArticle = new Article({ authorId: 10 });
const ability = defineAbilityFor(moderator);

ability.can('read', 'Article') // true
ability.can('update', 'Article', 'published') // true
ability.can('update', ownArticle, 'published') // true
ability.can('update', foreignArticle, 'title') // false

For more complex cases, you can use nested fields and wildcards, see for details

Checking logic

Let's consider a simple example where user can publish articles:

import { defineAbility } from '@casl/ability';
import { Article } from './entities';

const ability = defineAbility((can) => {
  can('read', 'Article', { published: true })
});
const article = new Article({ published: true });

ability.can('read', article); // (1)
ability.can('do', 'SomethingUndeclared'); // (2)
ability.can('read', 'Article'); // (3)

Line (1) returns true as we expected. Line (2) return false because for any unknown subject or action CASL returns false, by default everything is forbidden (if manage and all keywords are not used). But what would you expect line (3) to return? The answer may be unexpected for some of you, it returns true as well. Why?!

Let's get back to our experience for a while. Historically, majority of permissions management libs were built on top of roles or flags. So, a user either has permission or not. This can be expressed in pseudo code:

allow('read_article');
allow('update_article');

This is interpreted as "user can read ALL articles and user can update ALL articles". So, this is "all or nothing" mindset.

But CASL is different! It allows us to ask different questions to our permissions. So, when you check on a

  • subject, you ask "can I read THIS article?"
  • subject type, you ask "can I read SOME article?" (i.e., at least one article)

It's very useful when you don't have an instance to check but know its type (for example, during creation), so this allows your app to fail fast.

If you do checks on a subject type, you need to check permissions one more time on the final subject, right before sending request to API or database.

Inverted rules

This guide talk a lot about allowable permissions but nothing about disallowable ones. This is for the reason. The direct logic is much simpler to understand, and we recommend to use it whenever possible.

To define an inverted rule, you need to use the 2nd argument in the callback of defineAbility. Let's give a user a permission to do anything but not delete:

import { defineAbility } from '@casl/ability';

const ability = defineAbility((can, cannot) => {
  can('manage', 'all');
  cannot('delete', 'all');
});

ability.can('read', 'Post'); // true
ability.can('delete', 'Post'); // false

As you should know direct rules are checked by logical OR on the other hand inverted ones are checked by logical AND. So, in the example above user:

  • can do anything on all entities
  • and cannot delete any entity

When defining direct and inverted rules for the same pair of action and subject the order of rules matters: cannot declarations should follow after can, otherwise they will be overridden by can. For example, let's disallow to read all private objects (those that have property private = true):

const user = { id: 1 };
const ability = defineAbility((can, cannot) => {
  cannot('read', 'all', { private: true });
  can('read', 'all', { authorId: user.id });
});

ability.can('read', { private: true }); // false
ability.can('read', { authorId: user.id }); // true
ability.can('read', { authorId: user.id, private: true }); // true!

Here we got an unexpected result because direct rule is the last one. To fix the result just revert those rules and always remember to put inverted rules after the direct one!

Forbidden reasons

The good point about inverted rules is that they help to explicitly forbid particular actions. Moreover they allow to add explanation. Let's see how

import { defineAbility } from '@casl/ability';

export default defineAbility((can, cannot) => {
  can('read', 'all');
  cannot('read', 'all', { private: true })
    .because('You are not allowed to read private information');
});

We can get this message back by checking permissions using ForbiddenError:

import { ForbiddenError } from '@casl/ability';
import ability from './defineAbility';

try {
  ForbiddenError.from(ability).throwUnlessCan('read', { private: true })
} catch (error) {
  if (error instanceof ForbiddenError) {
    console.log(error.message); // You are not allowed to read private information
  }

  throw error
}

To learn more about ForbiddenError, see

Update rules

Sometimes, especially in frontend application development, we need to update Ability instance (e.g., on login or logout). To do this, we can use update method:

import ability from './defineAbility';

ability.update([]); // forbids everything
ability.update([ // switch to readonly mode
  { action: 'read', subject: 'all' }
]);

Also we can use AbilityBuilder to create rules:

import { createMongoAbility, AbilityBuilder } from '@casl/ability';

const ability = createMongoAbility();

const { can, rules } = new AbilityBuilder(createMongoAbility);
can('read', 'all');

ability.update(rules);

To track when rules are updated, we can subscribe to update (before ability is updated) or updated (after ability is updated) events of Ability instance:

const unsubscribe = ability.on('update', ({ rules, target }) => {
  // `rules` is an array passed to `update` method
  // `target` is a Ability instance that triggered event
})

unsubscribe() // removes subscription

What else?

CASL does not have a concept of "a role" and this makes it very powerful! As CASL allows to describe user abilities in your application, you can use it to:

  1. Implement feature toggles
    Hide unfinished feature or show it to beta testers only.
  2. Conduct A/B testing
    Based on age, region, country or whatever hide features for some users and show for others
  3. Simple Business Logic
    Disallow users to watch video if their subscription has been expired

See for details.

Ready for More?

We’ve briefly introduced all the features of CASL.js core - the rest of this guide will cover them and other advanced features with much finer details, so make sure to read through it all!