RBAC and ABAC are terms that security teams commonly throw around when discussing authorization and permissions systems. Most developers have heard them at some point and may have a sense for what they mean, but many aren't clear on how to think about RBAC and ABAC as tools for modeling permissions in their apps.
When building access control in an app, the common misunderstanding is that it's an "either/or" — that it's a decision between RBAC or ABAC — when in fact most applications want for both access control models.
This page will explain:
- What is role-based access control (RBAC)?
- What is attribute-based access control (ABAC)?
- What are common use cases and patterns for RBAC & ABAC, and what do you get from each model?
- Why should you think about using both RBAC and ABAC together?
What is Role-Based Access Control (RBAC)?
RBAC, which stands for Role-Based Access Control, is a broad classification for using roles to control the access that users have to resources. Most people are familiar with the concept of roles, and expect them to be a part of any authorization system. For many app developers, roles are the first and fastest step in implementing application authorization.
Roles can vary in how they are used, and how they are implemented. Because of this, "RBAC" doesn't have an exact meaning, and it can be confusing and even frustrating to see it characterized as a one-size-fits-all solution.
Roles provide a structure for assigning groups of permissions to users. They give us a repeatable way to assign those permissions, rather than listing them out to each user every time we want that user to have those permissions. When done well, a role can convey to a user an intuitive understanding of what she can expect to do in the application.
Imagine we're building a Customer Relationship Management (CRM) application like Salesforce.com. We want to have at least one person who can, for instance, add new users, delete users, and a maybe do a few other administrative tasks for the company. We could list out these permissions for this one person. (This pattern is actually called an Access Control List, or ACL.) But if we expect to give these same permissions to another person at the company in the future, we'll need to list them out all over again.
Instead, we can group these permissions into a single role, give that role a name – say, Admin – and assign that role to the people who are responsible for carrying out these tasks.
How Does RBAC Work?
Most applications start out with simple authorization models because it may not be clear what access patterns the users will want. Roles are a common framework to reach for because: a) most people know about them, and b) you can take a pretty big hammer to your access controls with them.
For instance, you might say that Users can only take actions foo, bar and baz in an application, whereas Admins can do everything. Problem solved 😎!
A typical implementation of this simple model is to store this role ("user" or "admin") in the users table in the database. We map the role to a set of permissions — i.e., what the role is allowed to do. We can configure this information statically in the application, or in some cases we might even enable users to configure custom roles where they get to choose what a role can do.
Finally, when the user tries to take an action, we enforce this control by checking what role the user has and whether the action is in the set of permissions for their role.
The limitations of RBAC
This simple model holds up for a short while. But over time, it degrades as common use cases don't quite fit.
For example, we hire a new type of an employee – say, an IT administrator – and we want to let her add and remove users from the CRM, but we don't want her to see any sales data. The quickest solution is to create a new role with just these permissions. No big deal. Now let's say we hire a contractor, and we want that person to be able to do data entry; we can make a scoped role for him too wherein he can enter data but can't delete data or do anything else...
As you can see, the overarching approach to this type of problem using roles is: as new scenarios come up, create new, very specific roles. This often leads bizarre groupings of permissions and other hard-to-debug roles setups many folks have encountered in the wild.
What's more, with this mapping of roles and permissions and the underlying model, developers are left with little choice but to expose a 2D matrix to their end-users, who then have to squint at it to figure out what role or combination of roles they should pick in order to do whatever they're trying to do.
What's more, the roles model is often insufficient because most applications' underlying data models don't easily fit in a 2D matrix. They're not a flat list of things a user can do. An application might need to traverse organizational hierarchies (VP>manager>etc.), navigate nested resources (parent company>subsidiary, etc.), and model other relationships that don't map to a simple list.
Let's take our CRM example again. We use this app to track information about prospects and customers – e.g., leads, contacts, deals, and accounts. Let's say we start with a small team. We have some sales reps, managers, and a VP.
- Sales Reps: can see and edit accounts and deals
- Managers: can additionally approve quotes
- VPs: can additionally assign/re-assign accounts
Over time, the team grows, and we decide we want to break it up regionally – North America, Europe and Asia. What do we do? We could add roles to reflect the intersection of each role and continent, which would look something like this:
- VP - North America
- VP - Europe
- VP - Asia
- Manager - North America
- Manager - Europe
And maybe we have a Global VP who needs the VP role for all regions.
Then we grow the team further and split it into junior and senior sales teams. Now we need to segment roles by level (VP, manager, etc.), region (Europe, etc.), and sub-team (junior vs. senior).
In an application like this, thinking about roles as a flat structure across the whole application can quickly lead to more roles than we care to manage.
Instead, we can think about roles as multidimensional data associated to data structures. For example, if we take our initial set of roles — Sales Reps, Managers, VPs — and associate them to a combination of user and a geographic region, then we can represent the desired granularity without introducing new roles.
Most RBAC implementations do not actually support this form of roles.
Traditionally, this kind of context-dependent role is instead considered a form of Attribute-Based Access Control (ABAC).
What is Attribute-Based Access Control (ABAC)?
Attribute-based access control (ABAC) is sufficiently broad in that it's both incredibly powerful and almost entirely meaningless and useless as a category of authorization. While many folks probably think they don't understand the term, they have probably used it in one form or another before.
ABAC is a way of controlling access to data based on properties (i.e., attributes) of the user and the resource she is accessing. A simple example might be "all users can read public data." In this case, "is public" is an attribute of the data.
Another example might be: "only users who are staff can read documents that are confidential." Here we specify the logic using both an attribute of the user – if she is staff or not – and a property of the data – is it confidential or not.
ABAC is also way of controlling access based on who the user is in relation to the resource or data she wants to access**.** "Users can edit their own files" is an example that may be familiar to many developers – it grants a permission (to edit) based on a relationship (ownership) of a resource (a file).
Given that attributes can be just about anything, ABAC spans so many scenarios that as a solution set it's not a terribly useful thing to point to. What is perhaps more useful, however, is to describe some of the patterns and use cases that come up frequently. We could use ABAC to implement access control for any of the following:
- Collaboration applications – e.g., 'a user can edit a file if she owns it or someone has given her editing privileges'
- A multi-tenant application – e.g., 'a user can read this data if she belongs to this company'
- Free vs. paid tiers – e.g., 'a user can access these features if she is a paid subscriber'
- Hierarchies – e.g., 'a user can view this data if she owns it or if she manages an employee that owns it'
- Impersonation – e.g., 'a user can view this data if she owns it or if she is a support engineer with permission to impersonate that user at this time'
- And so on
How Does ABAC Work?
Developers often "implement" ABAC without realizing it. For example, an if statement that checks whether the user is the file owner is actually a basic ABAC implementation.
ABAC is a superset of RBAC. Whereas RBAC is a flat 2D model that maps roles to a set of permissions, ABAC is multi-dimensional. So to implement an ABAC system, we need an interface that helps us express a potentially broader variety of rules about who can do what – specifically, we need one that lets us map both attributes on the user and attributes on the resource.
We can achieve this in a few different ways:
- Data in a database - We implement permissions by specifying constraints on the query – e.g., WHERE resource.owner_id = ? – or as a separate check in the application after data has been accessed.
- Fields from a token - We pass around cryptographic tokens (e.g., JWTs) containing information about the user and what she can do. Application code or proxies check for the presence of correct fields on the token. For example, it might check that a token has the correct scope – e.g., "read:account" in token.scopes.
- Policy engine - We express authorization logic using a policy language, and evaluate it over the relevant input data. For instance, if we're checking edit privileges on a file, we might be checking the metadata of that file to see if the user owns it. We might pass in the data manually, or pull it in from application context or a database. This approach lets us write fine-grained rules for what data the user is authorized to see, expressed as a function over the input data. This way, we keep the authorization logic separate from business logic, and we have a single place to manage access rules.
In practice, it's common to see ABAC and RBAC used together. To see how this works, let's go back to our CRM example.
The False Dichotomy of RBAC vs ABAC – Use Both
We previously used the crude approach of adding more roles to our CRM app to incorporate the concept of regions.
What if instead we created a region attribute and combined it with a role to achieve the same outcome? We could keep our 3 existing sales roles – sales rep, manager and VP – and anytime someone tries to access a piece of data or take a particular action, we check whether they have the appropriate role and whether they are a member of that region. This would allow us to keep our roles model and codebase relatively simple and easy to reason about, while also giving us the flexibility to add future attributes without worrying about how the model will scale.
In this way, we have combined RBAC and ABAC, which is the most common way the two approaches end up being used in the real world. Here are some other common patterns that call for a combination of RBAC and ABAC:
- Roles in a multi-tenant application: roles that only apply to users and resources within a particular tenant
- Role Hierarchies: more senior roles inherit permissions from less senior roles
- Resource-specific roles: roles that only apply to a particular resource
- Using roles with user groups: roles that apply to a group of users, rather than to an individual user
- Implied roles: roles that are assigned to users implicitly by the data model, rather than directly assigned
Building Access Control with oso
While developers have historically rolled their own authorization systems, many are now using oso to build authorization in their applications.
oso is a batteries-included authorization system that provides building blocks get things off the ground, and an extensible language that lets you extend the system to fit your needs. If you are adding, iterating on, or refactoring authorization in your application, here are some things you can do:
- Try the Quickstart for a 10-minute tutorial on oso
- Learn about built-in roles with oso and SQLAlchemy
- See some example apps in Node.js + Nest or Python + Flask
- Have a look at our our roles guide or ABAC policy examples using the oso policy language
- Recreate a bear of an authorization model, Github
- Join our Slack to chat with our engineering team