CASL. Permission management in express
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?”.
Update: CASL 4.0 is released, see “CASL 4.0. What’s inside?”
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/posts200 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=update200 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:
- Official Documentation
- Managing permissions with CASL in Vue app
- Managing permissions with CASL in React app
- Managing permissions with CASL in Angular app
- Managing permissions with CASL in Aurelia app
- Easy API Authorization with CASL and Feathers
- CASL and Cancan. Permissions sharing between UI and API
If you like that article, please consider to recommend it. 👏