Managing User Permissions in your React app

In authenticated frontend apps, we often want to change what’s visible to the user depending on his role. For example, a guest user might be able to see a post, but only a registered user or an admin sees a button to delete that post.

Managing this visibility may become a nightmare for UI application. You probably have written or seen code like this before:

if (user.role === ADMIN || user.auth && post.author === user.id) {
<button onClick={this.deletePost.bind(this}>Delete</button>
}

This code is spread over the application and usually become a big problem when Product Owner requests to add additional role in the app. Eventually you need to go through all such ifs and add additional check.

In this article, I will demonstrate an alternative way to implement permission management by using a neat library which is called CASL. It makes managing active user permissions very simply, and allows to rewrite previous example to this:

if (ability.can('delete', post)) {
<button onClick={this.deletePost.bind(this}>Delete</button>
}

If this is the first time you hear about CASL, you may want to read “What is CASL?” first.

Demo Application

As a demonstration, I’ve made a well known TodoApp. The permission rules for this app are:

  • a user can read and create any todo for any user
  • a user can update or delete only todos which are assigned to him (“me” option in dropdown)

I’ve used React.js with CASL to make these rules easy to implement and scale upon, in case other operations or entities are added in the future. If you’d like to see the result, check out this Github repo.

Now lets dive a bit deeper into implementation details.

Defining user permissions

First of all, lets define our user permissions in a file src/config/ability.js.

Let’s break down that code a bit:

The first argument passed to define method is an options object. When CASL checks an entity to determine permission, it needs to know the type of entity it’s looking at. The way to do it is to pass subjectName property inoptions. In our case, we are looking for custom __type property which defines object type (by default, CASL looks for property modelName or name on object’s constructor property).

The second argument is a DSL function which accepts 2 arguments: can and cannot(we don’t use in cannot this example). We define user permissions by calling can function. The first argument of can is an action (or an array of actions). Usually it will be a CRUD action but you can specify whatever is suitable for your application (e.g., you can define can('visit', '/protected/path') and later check ability.can('visit, '/protected/path')). The second argument is object type, in our case Todo, for which this rule is applicable.

Pay attention that in the second can function call, we pass a third argument which is called conditions. This is used to test if the specified assignee value matches an assignee property of an object we’ll provide when making the test (e.g., ability.can('read', todo)). If we didn’t do this, any todo could be updated or deleted by any user, not just the assignee.

Checking Permissions in React

At first, you may think that integration will be quite easy: just import created ability, addifs around some components in render function and we are done. That may work but only until you don’t change ability rules.

Why? Because React re-renders a component only in case if its state or props are changed. In our case we need to re-render components when ability rules are changed, because we will need to update permission rules when user does login or logout (or login under another account).

So, the way to go is to create a custom Can component. This component will have 2 props:

  • run which accepts action name
  • on which accepts subject (subject can be either a string or an object)

And 1 state propertyallowed which caches the check of ability.can(this.props.run, this.props.on).

If user has permission to run specified action on the subject, our component will render passed in children. Eventually we will be able to check permissions like this:

To track permission updates, we can utilize update event of Ability instance. We can do this with help of react hooks: componentWillMount and componentWillUnmount.:

We added setTimeout call because CASL emits update event before updating permissions. It will be possible to utilize updated event in 1.1.1 version of CASL.

The only thing which is left is to wrap your UI components in Can checks and that’s it.

To play around with your permissions logic, just open Dev Tools and type:

  • ability.update([]) to reset all permissions (i.e., readonly mode);
  • ability.update([{ subject: 'all', actions: 'manage' }]) to get full access to manage everything;
  • ability.rules to get a list of current abilities;
  • for more information about possible options and how to configure ability please read in official documentation and/or ask questions in gitter chat.

Wrap-up

With that, we have a really nice way of managing user permissions in a React app.

I believe <Can run="delete" on={this.props.todo}>...</Can> is much more readable and easier to understand then:

{ (user.role === ADMIN || user.auth && todo.assignee === user.id) &&
<button onClick={this.deleteTodo.bind(this}>Delete</button> }

With CASL we can be more explicit on what we do. Furthermore, such checks will for sure be used elsewhere in our app, and this is where CASL can help us.


Just write to read and be explicit in your code ;)