Getting authorization right with RBAC

Posted on 2021-07-24 byVlad Călin

Reading time of 1 minutes

A recurrent topic in the world of software development is authorization.

I will talk only about the topic of authorization, not authentication. Authorization is how systems figure out if certain users/actors should be allowed to do certain actions in the system, while authentication is about figuring out who the user/actor is and revolves around identity management.

Authorization is hard to get right, because you would have to take into consideration multiple things when designing a good authorization system:

  • what would be authorization patterns and requirements for your clients? Do they need very granular permissions, or you can get away with just admin and non-admin users?
  • how granular is too granular?
  • how are permission applied? Users will deal with resources inside the system, or they deal with global actions/resources.
  • how will the internal administration dashboard be used? Will you have multiple types of support people that will require other kind of permissions than the regular users? (eg. will you have account specific support people that need to be able to see and modify the resources owned by certain clients, or will you have support people responsible for entire sets of actions, such as data management and troubleshooting, some kind of configuration management, etc.) so they would require permission to manage certain types of resources at once, regardless of the owner.

These are big topics you need to explore when you start designing your permission system. Of course, you can start with a simple permission system (admin/non-admin - eg. Django's is_staff/is_superuser), then migrate later on to more flexible systems (eg. per-instance permissions, or RBAC - Role Based Access Control).

I did that a few times, and it was a pain. Migrating from a permission system to another is very hard and requires a lot of effort and development time. Suddenly, you need to revisit your whole app, figure out where all the permission checks are done (ideally, permission checking would be done in a centralized class/module), and having long and tiresome sessions where you discuss how the old permission system checks translate to the newer system, without breaking or frustrating the users.

Introducing RBAC

So what is it and what problem does it solve?

In systems, you need granularity for your permission system: certain actors can perform certain actions on certain resources. With each type of resources, you would have at least one permission per CRUD operation (create, read, update, delete). Systems that actually do something useful tend to have a lot of resources available. Combined with at least 4 permissions for each resource type, there would be a lot of permissions in the system to manage and assign to users.

This leads to increased complexity, becomes very error prone, and adding new users will be a nightmare. Adding a new user will require assigning all the permissions they need to get their job done, which more than often would be a lot of them.

As a solution for that, RBAC comes in: grouping multiple permissions in a single role, and then assigning roles to users. This is based on the assumption that users from the same teams will most likely require the same set of permissions on the same set of resources (in corporate environments, teams tend to own their own set of resources and shuld be forbidden from accessing other team's resources).

So how would it be implemented?

If you are starting from scratch, it's actually pretty simple:

  • have a way to define permissions, that should be either per resource or per resource type. AWS (for their IAM system) uses a combination of ARN (Amazon Resource Names) to identify the resource the permission is applied to, and a list of the granted permissions (depending on resource).

The resource can be also defined as a resource selector rather than just a single resource reference (eg. all instances with a certain tag, all containers that are in a cluster whose name starts with "dev-", etc). This offers a lot of flexibility when teams are able to create their own resources: when creating a new resource, you don't have to extend all the team's roles to also reference this new resource. Instead, as long as the newly created resource matches the resource selector, the job is already done.

  • have a way to group permissions under a single easy to remember name, traditionally named roles. This would be in fact just a named list of permissions. Then each user will be able to reference one or more such roles.

  • have an internal unified way to easily check if a certain user/actor is allowed to do a specific action on a specific item. (has_permission(user.id, "read", resource.id)). This will simplify development by having a single entry point for the permission checking.

Having all these will lead to a robust but flexible system for managing permissions for users (external users / clients and internal users / employees as well).

The only thing left to do is figure out what the sensible defaults would be, because setting up a new account from scratch means creating all the default roles with the default permissions assigned every time. This can be automized, but once you do that, changing the defaults will be a pain: adding/removing permissions from the default roles or adding/removing default roles might require going back to all existent accounts and apply the change retroactively. If not, older clients might end up not having these new permissions/roles, and their experience might get impacted. Of course, it depends on the system, but this is something to keep in mind when changing defaults.

Alternatives to RBAC

Most modern systems use a RBAC based authorization mechanism (eg. cloud providers such as AWS, Google Cloud, other SaaS services such as Gitlab, Github).

But there are alternatives that might be more suited to your case:

  • ALC (Access control lists) - for simpler systems, having simple lists per resource with who can access what might be enough, but this will not scale. Adding new users and resources trigger system-wide updates, which for a high number of users or resources, it will slowly cripple the system.
  • ABAC (Attribute based access control) - This method is usually considered as being an alternative to RBAC, and this is why I put it here in this list. But in fact, I consider it being a complementary solution on top of RBAC, since it is a method of resource targeting (targeting which resource is references by a permission rule, based on boolean logic) vs RBAC, which is a method of organizing permission rules at the system level. You can see that these two techniques can work perfectly well together and offer a more powerful solution for managing authorization. They are not alternatives to each other because they solve different problems.

Conclusion

If you need to implement authorization in a system, you can't go wrong with RBAC. It scales with your system, it is flexible enough to support a wide variation of access patterns and requirements from your users, and robust enough to support a huge number of users, permissions and resources.

Most enterprises use it, so it also has an extra added benefit: familiarity. Users already know how to deal with authorization frameworks based on RBAC and they will be able to configure it without guidance and support from your side more often.

Using RBAC will allow your system to scale and offers the flexibility and robustness needed for such a system. After all, authorization is usually the most critical system inside a system. Leaking data must be avoided at all costs and proper authorization can not be an afterthought.