So you’re building a system-as-a-service (SaaS) product and want to serve real customers and start making revenue. But first, you’ll need to get some of the basics squared away. That’s where role-based access control (RBAC) comes into play, helping you to strengthen your security, simplify your user management, and allow for scalability.
This article explains what RBAC is, why you need it, and how to implement it in a way that’ll scale as your product matures.
What is RBAC?
Role-based access control (RBAC) is a system that helps you organize permissions and specify who’s allowed to do what. For example, your product almost certainly has features not all users are allowed to use, like resetting other users’ passwords or viewing coworkers’ salaries.
As your product becomes more complex and serves more customers, admins for those customers need granular rules, so you need a system that’s a bit more solid than hundreds of ad-hoc gates in your product.
There are 3 basic concepts:
Role: a job function or title, which defines a person’s authority level
Permission: the ability to view or modify a class of functionality
Operation: a specific action in your system, which may require multiple permissions to be usable
Let’s assume you’re a product manager at Contentsquare. As a PM, you’re assigned to an analyst role, which has many permissions, one of which is the ability to modify reports. There are a few different places in the interface where you can modify reports, so each of those different places would be an operation.
These concepts have the following relationships:
A user has many roles (A PM, for example, has the analyst role)
A role has many permissions (the analyst role has the ‘Edit Report’ permission)
A permission has many operations (‘Edit Event Definition’ permission enables the ‘Edit Report Name’ and ‘Edit Report Criteria’ operations)
This means giving a user one— or more than one—role grants them a set of permissions, which in turn grants them some set of operations in your product.
Why do you need to use RBAC?
RBAC allows you to create a permissions system that follows human organizational structures well, keeping it more up-to-date over time than other models. It’s also intuitive to use, making it easy to configure correctly.
One alternative model is an access control list (ACL), in which you explicitly list all the operations a particular user is allowed to do. Under ACL, a user might start in marketing and be given permits to marketing systems. Then, a few months later they transfer departments and need access to Salesforce. It’s hard for an IT department to know which permissions are still needed, so in the interest of not breaking the user’s account, they will simply add the new permissions in addition to the old ones.
However, in RBAC, if the user’s role was changed from ‘marketer’ to ‘salesperson’, their access would be correctly updated across the board.
In the other direction, if marketing added a new tool and all users needed access to it, in an ACL world you’d need to add a permit to each marketer, but in RBAC you can add it to the role and be good to go.
How to implement RBAC
Here are a few simple guidelines on how to implement RBAC in a way that’ll scale as your product matures.
1. Check operations on permissions, not roles
This is the most important thing. You’re going to need to add permission
checks to each RBAC operation in your codebase—potentially hundreds of places. Make sure to do it in terms of permissions, which should exist in a first-class form in your codebase.
For example, you never want to have code that looks like this:
if (User.role in ['admin', 'marketer']) { ... }
You always want your code to look like:
if (User.can(Permissions.EDIT_REPORT, report) { ... }
Because a role allows you to aggregate as many permissions as you need, the permissions themselves can be extremely granular. This allows you to fluidly move permissions between roles, or create new roles easily without needing to change your front-end code. You can even make custom roles for different customers, based on how they want the product to be accessed, without changing any front-end code.
2. Permissions should grant access additively
There’s likely some baseline functionality that every single user of your application can do. (For some sensitive apps, this might be extremely limited, for example logging in and nothing else.) This is what all users should start with, and nothing more.
Beyond that baseline, permissions should grant access additively, never negatively. Negative permissions cause conflicts if a subject has more than one role, since it’s unclear which role ‘wins’, and resolving that conflict creates many problems.
3. Optimize extensions
As your product matures, you may want to incorporate some additional concepts into your permissions system. If you’ve followed the above rules, these will be straightforward to add.
Custom roles
It’s possible that each of your users would like to have a different role definition. For example, by default at Contentsquare, analysts can modify and delete reports in any way. But perhaps another company wouldn’t like to allow analysts to delete or recategorize reports. The way you implement this at a technical level is by storing each custom role in your database, as such:
Table "public.role"
Column | Type
--------------------------------------------+----------------
id | bigint
customer_id | bigint
name | text
description | text
create_report | boolean
modify_report | boolean
modify_report_name | boolean
modify_report_category | boolean
modify_report_note | boolean
modify_report_report | boolean
delete_report | boolean
By adding rows to this table, your team can create fully customized roles for each user. You’ll need a shim to load the custom roles, but not much else. If you’ve written your code to always check operations against permissions, instead of roles, you can now support this custom role with no change to your frontend.
These custom roles come with some maintenance overhead. When you add new permissions, you need to consider whether they should be enabled for each custom role. A careful backfill is required to ensure these roles get the appropriate permissions. Sometimes, this requires product-level decision-making. As a general rule, err on the side of restricting access when configuring roles for enterprise customers.
Groups and resources
It’s possible you may wish for a group of users to all have the same set of roles. For example, if you have researchers across different departments, they might all be part of a research group that gives them analyst and consumer roles.
Many of these permissions are ideally applied on a per-resource basis. For example, if your company has 10 different projects, some users may need read access to certain projects but not others. There are many ways to express this but the cleanest is to use groups, so you don’t need to create a mapping between each individual user and the resources they’re permitted to access.
For example, marketing researchers should be able to read and write marketing projects, and product researchers should be able to read and write within their own projects. Then, the base researcher group would grant some limited base permissions, and the marketing researcher group would be associated with a set of resources. The set of available resources can then be changed automatically.
If you’ve written your permission sets to be positive, never negative, a group is an easy indirection to introduce: it’s just a union of some other permission sets.
For example, our code architecture also makes it easy to add per-resource restrictions. Consider the permission check code from above:
if (User.can(Permissions.EDIT_REPORT, report) { ... }
Note that we’re passing in the resource—the report the user is attempting to edit—rather than just the edit permission. This allows the permissions machinery to check between the user, the permission, and the resource in question. All of this logic lives in that permissions code, and the rest of our product code didn’t need to change when we added the cross-cutting new concept of per-resource restrictions.
We’re an international team of content experts and writers with a passion for all things customer experience (CX). From best practices to the hottest trends in digital, we’ve got it covered. Explore our guides to learn everything you need to know to create experiences that your customers will love. Happy reading!