Implementing RBAC Authorization in NodeJS
Hello everyone,
In this blog post we will look into how we can implement Authorization in NodeJS application(express web app).
we will not be looking into Authentication part, we will assume that user has already been authenticated(JWT, Session, Cookie, SAML Token etc).
So first take quick look into RBAC, Rule Based Access Control is a mechanisms to allow/restrict access to particular resource based the permission assigned to roles and user having that role.
As shown in above image, user can have one role, and a role can have multiple permission and permission can belong to multiple role. Defining permission depends on the granular control that you want to have in your application. Permission can be post.create
post.edit.own
post.edit.all
post.delete
.
Now that we are clear with RBAC, let’s get to its implementation. So there are two ways we can do this.
- Create parameterized middleware, which accept permission as arguments and check against user permission.
- Create single middleware which has all permission and its allowed URL and Method.
Let’s dig deep into these method.
Create parameterized middleware
In this we basically create one middleware which accepts the permission as argument and then we check if current user has that permission, if it has that then we will allow the operation otherwise throw the error as permission denied.
// Middleware builder for express
function authZ(permission){
return (req, res, next)=>{
const user = req.user; // This would have been populated in authentication middleware
if(user.permissions.includes(permission)){
next();
}else{
res.status(403).json({message: 'Permission denied to access this resource'});
}
}
}
// Express Routes
route.get('post',authZ('post.get'), PostHandler.getList);
route.get('post/:id',authZ('post.get'), PostHandler.getById);
route.post('post',authZ('post.create'), PostHandler.create);
route.put('post',authZ('post.update'), PostHandler.update);
route.delete('post',authZ('post.delete'), PostHandler.delete);
In this approach, you will have very granular control with any complexity and this seems to be very simple and easy to understand approach. but this has its own problem when scaling you application and handling permission at each routes becomes very difficult and hard to reason about and do the audit.
Create single middleware which has all permission
In this type of implementation we will create a general middleware which is applied to all routes(group of routes). In this we will have one configuration file which contains all routes and its required permission for each method.
{
"/post":{
'GET':'post.get',
'POST': 'post.create',
'PUT': 'post.update',
'DELETE': 'post.delete'
},
"/post/:id":{
'GET':'post.get'
}
...
}
const { match } = require('node-match-path')
function authZ(req, res, next){
const matchFound = '';
Object.keys(config).forEach(url=>{
if(match(url, req.url).matches){
matchFound=url;
}
});
const permission = config[matchFound][req.method];
const url = req.path;
const user = req.user; // This would have been populated in authentication middleware
if(user.permissions.includes(permission)){
next();
}else{
res.status(403).json({message: 'Permission denied to access this resource'});
}
}
// Express Routes
route.use(authZ);// This can also be set on main express app
route.get('post', PostHandler.getList);
route.get('post/:id', PostHandler.getById);
route.post('post', PostHandler.create);
route.put('post', PostHandler.update);
route.delete('post', PostHandler.delete);
As you can see with this approach, you don’t have to set the permission in each routes and you will also have one added benefit of having all routes in single place to understand your project complexity.
I have hardcoded application configuration in this example, but you can make this dynamic and fetch it from different API, or DB at regular interval so that you don’t have to restart your application to update permission, which is not feasible in first approach.
Thanks for reading…