Authorization with CASL in an express app

CASL Expressjs API

In this article I will show how to integrate CASL in an expressjs app
But let’s start from clarifying what is what:

  • Authorization is the process of giving someone permission to do or have something.
  • CASL is an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access.
  • Express is a fast, unopinionated, minimalist web framework for Node.js.
If this is first time you’re learning about CASL, please read “What is CASL?”.

Create blog application

I’ve prepared an example of blog application integrated with CASL on github. The app consists of 3 entities (User, Post and Comment) and 4 modules (one module per entity and one module for authentication and authorization logic). All modules can be found in src/modules folder. Application uses mongoose models, passport authentication and implements basic REST API interface which allows to:

  • read Posts and Comments for anybody
  • create new Users for anybody
  • manage own Posts for logged in users
  • update and read personal information for logged in users
  • create, update, delete own Comments for logged in users

In order to install this application, just clone it from github, run npm install and npm start. Also you need MongoDB to be up and running, app will connect to mongodb://localhost:27017/blog. After everything is ready, we can start to play: try to get or create /posts, see errors and results. In order to make this stuff a bit more interesting you can import basic data in your database from db folder:

mongorestore ./db

Alternatively you can follow login instructions in project README or use my Postman collection to setup everything. Now you can log in with casl@medium.com/password credentials by sending POST /session request. Then put returned accessToken in Authorization header and now you can write posts and leave comments :)

CASL authorization

I merged authentication and authorization logic into one piece, so that I can define permissions for both logged in and anonymous users. In case if user doesn’t specify token in Authorization header, I use lazy generated BLANK_TOKEN and empty User instance.

Lets check src/modules/auth/abilities.js file which exports express middleware and defines abilities for logged in and anonymous users:

Take a look at defineAbilitiesFor function which is responsible for ability definition. I created AbilityBuidler instance using its extract method what allows me to write the whole function in DSL style. If user is passed in, then he will be able to manage own Comments and Posts, otherwise more general rules are applied (and later used as anonymous user’s ability).

Now lets try to update own Post (I have logged in under casl@medium.com):

PATCH http://localhost:3030/posts/597649a88679237e6f411ae6
{
"post": {
"title": "[UPDATED] my post title"
}
}
200 Ok
{
"post": {
"_id": "597649a88679237e6f411ae6",
"updatedAt": "2017-07-24T19:53:09.693Z",
"createdAt": "2017-07-24T19:25:28.766Z",
"title": "[UPDATED] my post title",
"text": "very long and interesting text",
"author": "597648b99d24c87e51aecec3",
"__v": 0
}
}

All good but what will it happen if I try to update somebody else post? :)

PATCH http://localhost:3030/posts/59761ba80203fb638e9bd85c
{
"post": {
"title": "[EVIL ACTION] my post title"
}
}
403 Ok
{
"status": "forbidden",
"message": "Cannot execute \"update\" on \"Post\""
}

Looks like it’s impossible… Cool!

Now lets imagine that we build an admin interface for our writers and on some page, we need to show them only posts which they are allowed to edit. I will try to change abilities to this:

This way all authenticated users will see only what they created and anonymous users will see everything. It seems wrong but lets just try it:

GET http://localhost:3030/posts
200 Ok
{
"posts": [
{
"_id": "597649a88679237e6f411ae6",
"updatedAt": "2017-07-24T19:53:09.693Z",
"createdAt": "2017-07-24T19:25:28.766Z",
"title": "[UPDATED] my post title",
"text": "very long and interesting text",
"author": "597648b99d24c87e51aecec3",
"__v": 0
}
]
}

I changed only few lines of code in permission logic and all parts of application reacted to this without additional changes. This is possible thanks to accessibleBy method which CASL provides as part of mongoose plugin. See Database Integration for details.

You can play and try different combinations of abilities, for example, try to give access to create comments for anonymous users. Check Defining Abilities in the official documentation for more details.

Ok, now lets revert everything to how it was and add additional action parameter to /posts route. To do this open src/modules/posts/service.js file, find accessibleBy in findAll method and change it to

Post.accessibleBy(req.ability, req.query.action)

Restart the server (if you don’t use dev mode) and now lets try to send GET /posts?action=update, we should see only posts which are allowed to be udpated:

GET http://localhost:3030/posts?action=update
200 Ok
{
"posts": [
{
"_id": "597649a88679237e6f411ae6",
"updatedAt": "2017-07-24T19:53:09.693Z",
"createdAt": "2017-07-24T19:25:28.766Z",
"title": "[UPDATED] my post title",
"text": "very long and interesting text",
"author": "597648b99d24c87e51aecec3",
"__v": 0
}
]
}

And GET /posts as before returns all posts which can be read by currently logged in user. Exactly what we have been trying to achieve with first change!

Conclusion

I hope it was an interesting journey and now you like CASL as much as I do :). CASL has pretty good documentation, so I believe you will find a lot of useful information there but don’t hesitate to ask questions in gitter chat if there are any.

Looking for more?

Read documentation and other articles about CASL:

If you like that article, please consider to recommend it. 👏