CASL: How to manage user permissions in React app - Basics
Tags: react, typescript, frontend, tutorial
Published: 2023-06-16 10:26:55


### Why would I need a CASL in my app

If I would ask you to add managing user permissions in the existing React app, how do you solve it in the first place? In my case, it would have been [the most react-ish way](https://react.dev/learn/reusing-logic-with-custom-hooks):
1. Write a hook that encapsulates the logic of getting a user role.
2. Use this hook inside a component to check the user role for correspondence.
```ts
const useRole = () => {
  const user = useUser()
  
  return user.role
}

const Component = () => {
  const role = useRole()

  if (role === 'admin') {
    return <AdminComponent />
  }

  return <PublicComponent />
}
```
As much as the application grows, the logic of displaying or hiding some parts of the application becomes more complex. Tomorrow you will have two more roles that will need to check, the next day the logic of role permissions will change, for example, you would need to also check the status of the subscription. And for every permissions-based functionality, you will need to change all conditions to correspond to the new requirements. Sounds like we're doing something wrong, isn't it?

It is. We're breaking [the separation of concerns principle](https://en.wikipedia.org/wiki/Separation_of_concerns).

We're mixing the logic that handles permissions for different roles and the logic that handles providing abilities to users that has permissions for them.

### And that's where CASL barge in!

[CASL](https://casl.js.org/) is a JavaScript library that allows to limit some actions for users based on their attributes. It can be not only their role but pretty much any attribute they have. Let's try to build some permissions manager using CASL.

#### How to start with CASL

First of all, let's add CASL to our project:
```
npm install @casl/ability @casl/react
# or
yarn add @casl/ability @casl/react
```
CASL itself is not attached to any framework, so it needs an adapter to work with React elements.
Now, let's create `ability.ts` file and define abilities for the user:

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

export function defineAbility() {
  const { can, build } = new AbilityBuilder(createMongoAbility)

  can('read', 'all')
  can('update', 'article')

  return build()
};
```
So, here we're defining what exactly our user could do. To do it, we create an instance of `AbilityBuilder` class, and describe permissions using `can` function. We want our users to be able to view everything in the app and edit the article.

CASL is operating such a thing as ability. Ability is the description of permission, which describes using 4 parameters, but for now, we only need two: Action and Subject. Action is what we can do, and Subject is what we can interact with.

What can be Action and Subject? Basically - anything! It's a layer of abstraction, so you can define what Actions and Subjects are more suitable for your needs. Also, there is a special keywords that represent any Action and Subject: `manage` is representing all Actions, and `all` represents all Subjects.

Currently, we have the same ability for all users. Let's define more abilities and base them on the user's attributes and roles. Let our app be a blog where different users can post their articles and read other articles. Also, we will add admin and manager roles, which will have wider abilities.

```ts
type User = {
  role: 'admin' | 'manager' | 'author'
  isAuthorised: boolean
}

type CrudActions = 'create' | 'read' | 'update' | 'delete' | 'manage'
type Ability = 
  | [CrudActions, 'article']
  | [CrudActions, 'profile']
  | [CrudActions, 'all']

export type AppAbility = PureAbility<Ability, MongoQuery>

export function defineAbilityFor(user: User) {
  const { can, cannot, build } = new AbilityBuilder<AppAbility>(createMongoAbility)

  can("read", "all");
  can("create", "profile");

  if (user.role === "admin") {
    can("manage", "all");
  }
  if (user.role === "manager") {
    can("manage", "article");
  }
  if (user.isAuthorised) {
    can("update", "profile");
    can(["create", "update"], "article");
    cannot("create", "profile");
  }


  return build()
};
```

We update the function and now it takes a user and manages abilities based on user attributes such as roles and authorization. We also used `cannot` function which is an inverted `can` function.

Let's check how our manager works by checking permissions for different kinds of users.

```ts
const admin = defineAbilityFor({ isAuthorised: true, role: "admin" });
admin.can("read", "article")   // true
admin.can("update", "profile") // true
admin.can("delete", "article") // true
admin.can("delete", "profile") // true
admin.can("create", "profile") // false

const manager = defineAbilityFor({ isAuthorised: true, role: "manager" });
manager.can("read", "article")   // true
manager.can("update", "profile") // true
manager.can("delete", "article") // true
manager.can("delete", "profile") // false
manager.can("create", "profile") // false

const author = defineAbilityFor({ isAuthorised: true, role: "author" });
author.can("read", "article")   // true
author.can("update", "profile") // true
author.can("delete", "article") // false
author.can("delete", "profile") // false
author.can("create", "profile") // false


const reader = defineAbilityFor({ isAuthorised: false });
reader.can("read", "article")   // true
reader.can("update", "profile") // false
reader.can("delete", "article") // false
reader.can("delete", "profile") // false
reader.can("create", "profile") // true
```

You can use both `can` and `cannot` to check ability or its absence, `manage` and `all` keywords can be used too.

OK, now we are sure that every user type has permissions he should have.  What's next?

#### How to use it in React app

You can already use it with React by providing an ability instance to component and conditionally do something:

```tsx
const Article = () => {
  const user = useUser()
  const { can, cannot } = defineAbilityFor(user)
  return (
    <nav>
      <a href="/article/all">All articles</a>
      {can('create', 'article') && (
        <a href="/article/new">Write an article</a>
      )}
      {can('read', 'profile') && (
        <a href="/profile">My profile</a>
      )}
      {cannot('read', 'profile') && (
        <a href="/login">Login</a>
      )}
    <nav>
  )
}
```

But using `@casl/react`, it can be even better. This package contains `Can` component which does the same as the example above - show or hide elements.

```tsx
const Article = () => {
  const user = useUser()
  const ability = defineAbilityFor(user)

  return (
    <nav>
      <a href="/article/all">All articles</a>
      <Can I="create" an="article" ability={ability}>
        <a href="/article/new">Write an article</a>
      </Can>
      <Can I="read" a="profile" ability={ability}>
        <a href="/profile">My profile</a>
      </Can>
      <Can not I="read" a="profile" ability={ability}>
        <a href="/login">Login</a>
      </Can>
    <nav>
  )
}
```

Wow, this becomes a lot more human-readable with these `I` and `a` props. But it only aliases to pass subjects and actions. Actually, instead of `I`, you can use `do`, and instead of `a` - `an`, `this`, and `on`.

You can get rid of passing `ability` every time using `Can` by bounding ability to it or even bound a context that contains ability. This bounding is covered in [documentation](https://casl.js.org/v6/en/package/casl-react#bind-can-to-a-particular-ability-instance).

#### Conclusion

CASL is great library that allows to manage permissions in most efficient way, it has well documented API and adaptation to many frontend libraries and frameworks. In this article I covered the basic usage of it, in the next I'll cover more advanced usage cases. See you later!