Bitflags for permissions

Tuesday, February 16, 2021

Background

Recently I revisited a way to deal with permissions in an application. Miguel Grinberg's Flask Web Development book presented a way to do this with so-called "bit flags". I did not fully understand how this was implemented until last night. It's quite a clever way of dealing with permissions in an application. In order for me to understand how this works I needed to look more into "bit flags". So this post will cover my understanding of this interesting data structure.

Before going further I'm just going to emphasize that my understanding really hinges on two data sources. First being the Flask book and the second being this article from a Nasa site. But before I dive into the details let's just talk about the high-level concepts:

  1. Roles: a set of permissions defines a role for a user in an application
  2. Permissions: an individual marker identifying that a user can do something in application.

It probably seems weird to go into defining these things because we are all so intimately familiar with the concept of roles and permissions. They exist in nearly every application that we use on regular basis. But I still find it helpful to keep in mind that a role for a user in an application is comprised of many permissions. Discord is a great example of an application that is highly configurable through custom roles.

Naive approach at permissions

So how can we represent a users permissions in our application? The upshot is that with "bit flags" you can uniquely represent the users role with a single integer value. But let's think about this a little bit. Let's say that we define some permissions like so:

COMMENT = 1
WRITE   = 2
EDIT    = 3
ADMIN   = 4
SUPER   = 5

Awesome, we've defined 5 permissions in our application. And each one of them has an integer value assigned to it. Now we have to think about roles. How could we represent a "standard" user role in our application? Let's say that a standard user role allows commenting, writing and editing. There are a couple ways to think about that users role:

  1. Add the values together. COMMENT + WRITE + EDIT = 6. Therefore this standard user role is represented by the value 6. This is what I said we could do earlier, we could represent all permissions in the application as a single integer value. (But this has issues)
  2. Store a list of the permissions that the user has, [1,2,3].

How do we reverse this though? What if we needed to figure out which permissions a user has? The addition method has a glaring problem. There is more than one way to add the permissions such that the end result is 6. I.e. COMMENT + SUPER = 6 and WRITE + ADMIN = 6. So simple addition looks like it's out (but we will find later all hope is not lost).

With the list of permissions approach we can at least uniquely identify the roles that user has, but it has a different problem. For every standard user we need to store at least 3 values. It could be up to 5 values if the person is also an ADMIN and a SUPER. Add even more if your application allows more, i.e. Discord and Jira. Everywhere you need to pass the info about your users role, you would need to send these 3 values. And in application that has a lot of users that can become burdensome.

An improved approach at handling permissions

How can this be done better? This is where bit flags and bitwise operators come in. Let's change up the underlying representation of our permissions and dive into this approach:

COMMENT = 0b00000001 (binary) = 1
WRITE   = 0b00000010          = 2
EDIT    = 0b00000100          = 4
ADMIN   = 0b00001000          = 8
SUPER   = 0b10000000          = 128

What's different? Well we chose to represent the permissions in binary. In other words, each permission is a power of 2. This has the solves the issue with adding the values of the roles. Therefor each sum of the permissions will end up unique. I.e. our standard role no longer conflicts with the ADMIN permission:

COMMENT + WRITE + EDIT = 7

So great, we've solved that issue. We can now store a single integer in our database for every user of our application, and it will uniquely represent the persons role. But we need to be able to check this persons role to customize their experience in our application. How can we do that? It turns out that we can actually get some more utility out of using binary numbers to represent roles. We can use bitwise operators as well.

Bitwise operations for working with permissions

The upshot here is that bitwise operations are very efficient operations computationally, they are also built-in to the foundation every programming language. Here's an example in Elixir of how we can combine the permissions for our standard user role:

use Bitwise
comment = 0b00000001
write = 0b00000010
edit = 0b00000100
standard_user = comment ||| write ||| edit

The ||| is called the bitwise OR operator, this is the particular symbol that Elixir uses for "bitwise OR". It will be different in every language. We can read this literally as the standard user can comment or write or edit.

Now imagine that a standard user is going to edit a post that they created on our site. How would our application check that this person is allowed to do that?

iex(20)> standard_user &&& edit
4

What if the user is trying to access an admin-only dashboard?

iex(21)> standard_user &&& admin
0

What if the user is trying to do super-only action?

iex(22)> standard_user &&& super
0

This &&& operator is the bitwise AND operator for Elixir. What you can see from the outputs above is that if a user has the permission we are testing, it returns the integer value of that permission. If a user does not have the permission we get back 0. This gives a very efficient and convenient method of checking if a user is authorized for a given task in an application.

It's worth revisiting the list of permissions we discussed earlier, and comparing the approach for how we would check a users permissions in that scenario. First and foremost we would need to create a list in memory:

>>> roles = [1, 2, 4]

Then we would need to check if the required role for a given action in the application is contained in that list:

>>> admin in [1, 2, 4]

To me this doesn't seem too much worse. I suppose a downside is that if your application grows and you need to add more permissions, you will need to adjust your database model to have a new column. Otherwise you might be able to make use of JSON column type, and store the list directly. Either scenario seems more complex than using the binary representation which involves storing a single integer value in your database. The point is that using a list has more tradeoffs than just performance and memory optimizations, some of which I probably have not even considered.

Conclusions and references

My next goals are to incorporate this into a database model, and see how to develop a model within Elixir and Phoenix that makes use of this type of a permissions scheme. I am not an expert on this method of permissions. I am merely reflecting what I have learned from these resources, which I recommend checking out:

  1. https://flaskbook.com/#
  2. https://mplnet.gsfc.nasa.gov/about-flags
  3. https://en.wikipedia.org/wiki/Bitwise_operation#Bitwise_operators

If I have missed something or you would like to chat about anything in this post, please feel free to email me at wgwz@pm.me.