Permission Bitmasks: An Introduction

Janani Subbiah
Dev Genius
Published in
5 min readJun 1, 2023

--

Photo by Isaac Quesada on Unsplash

Roles and permissions for the purposes of Role Based Access Control (RBAC) is a very common requirement/feature for a lot of applications. As a part of this blog post I want to provide a very simple introduction to one specific implementation of roles and permissions in a datastore.

The whole idea of permission bitmasks is to map bit positions to permissions. We start by identifying resources in our system followed by defining what actions we want to allow on those resources.

Step 1: Resource Identification

To simplify this let us consider a library management system that manages information on a bunch of books. So our resource here would be books.

Step 2: Operations on Resource

Next we can define actions/operations we want to allow on books: Create, Read, Update and Delete.

Step 3: Next we define bit positions for our identified operations.

Create: Bit 0
Read: Bit 1
Update: Bit 2
Delete: Bit 3

Each of these bits can have one of two values: 0 or 1. A zero in a bit position would mean that the user does not have the permission to perform the operation that is denoted by that bit position. For example, if User A has a value of 0 in bit position 2, we can conclude that User A cannot update books. This might seem a confusing right now, but the hope is following sections can clarify any questions you may have right now!

Step 4: Assign Permission Decimals

This step is probably one of the most crucial that actually brings the whole concept home.

For every user in the system we assign permissions i.e. we assign 0s or 1s in all the bit positions we have identified above and compute the decimal value for the same.

For example, User A can create and read books information but cannot update or delete them. So we start of by assigning 0s and 1s to bit positions:

Bit 0: 1
Bit 1: 1
Bit 2: 0
Bit 3: 0

So the binary value of their permission would be: 0011. If we were to convert this to a decimal value we get 2. So the value “2” will be assigned as User A’s permission within the library management system.

This way every user in the System will get a decimal value that will denote what books operations they are authorized to do.

Step 5: Authorization Rules using Permission Bitmasks

The final step is to enforce the rules defined above! Say we have APIs to create, read, update and delete books. On each of these APIs we will have authorization rules that will make sure the bit position the operation corresponds to is 1 and not 0. For example, when User A accesses the read books API, the authorization rule check will be to make sure the permission value for User A (which we computed to be 2 in the previous step), has a value of 1 in the 1st bit position. So 2 will be converted to its corresponding binary value (0011) and bit position 1 is checked. If the value is 1 then the request is allowed to be processed further. If it is 0, then a 403 (forbidden) is returned.

Step 6: Extending Permission Bitmasks to Other Resource

In our example we only talked about a single resource: books. But we can extend this whole concept to many more resources. A unique bit position will need to be assigned to every resource and operation combination!

Step 7: Introducing Roles

In order to make managing permissions easier, we can introduce a concept of roles. For all intents and purposes, roles are nothing but a collection of permissions.

Instead of assigning permission decimal values to users directly, we can introduce a layer of abstraction: Roles, that instead will contain the permission decimal value and users will be assigned those roles instead. This abstraction truly shines when you have to update permissions for a set of users. Without roles, every user in the system may need to have their permission decimal value recomputed and reassigned. With roles, because there is no direct mapping between permission values and users, we might just have to recompute and reassign the value of one of more roles and all users with those roles have now been updated!

For example, let us introduce “Customer” and “Staff” as two possible roles in the library management system. Customers can only read books information while Staff can create, read, update and delete books information. So we introduce roles with their corresponding permission decimal value:

Customer => 0001 => 1
Staff => 1111 => 15

We then assign these roles to all users in our system.

If we were to make updates to the system to not allow staffs to delete books, we would recompute and reassign the value of the Staff role and all the users with that role will not get the new values automatically:

Staff => 0111 => 7

Without roles, we would have had to assign the new value (7) to all staff users in our system.

Permission bitmasks are a very efficient way to implement permissions and roles. IMO they truly shine when there are data storage shortages. If you are concerned about the amount of storage space taken up by your data, permission bitmasks can be great way to cut back on space! Permission bitmasks also provide an excellent abstraction if you do not want to expose your permission model in direct human readable form. Unless someone know what bit is assigned to what position, it is almost useless to know that User A has a permission value of 4.

But there is a catch: A learning curve. Depending on the team looking to implement and manage permissions, bitmasks can pose a considerable learning curve. They can also get complicated over time especially if there are many roles, resources and operations to manage. Finally reusing a bit position for a different permission could lead to more headache based on how complex the permission model is. For example, if we decide we want to delete the API to delete books, the permission value assigned to bit 3 becomes pointless. We have three options here:

  • Leave that bit as is and make sure no part of the code uses it.
  • Set that bit value to 0 which in this case would mean no user can delete a book. Followed by the necessary re-computations and re-assignments of roles values.
  • Move the permission bits by one. So all the bits that come after bit 3 will get shifted by one position to the right. For example, if bit 4 was assigned to operation X on resource 3, with the new assignment, bit 3 will be assigned to operation X on resource 3. This way all bit positions are assigned to permissions needed and used by the application. The biggest and most obvious downside is re-computation of all role permission values could get tedious depending on the complexity of the permission model.

All the above solutions have a downside. None of them are an A-ok solution and one might have to pick and choose what works best for their application.

--

--