Building multi-tenant authorization system for B2B SaaS in Go using Permify

In this blog, we will look at how we implemented an open-source multi-tenant authorization service based on Google’s Zanzibar. We will talk about the architecture and how to use it to implement the Role Based Access Control (RBAC) policies.

We were developing a multi-tenant SaaS product for one of our clients, and we wanted to employ a robust, easy-to-use authorization mechanism. Initially, we used a very basic approach of storing the ID of the user who created the resource and checking access against it. However, as the application grew, so did the types of resources. The relationship among the resources became complex, and the access queries/patterns became quite varied. We needed to support user-level authorization as well as team or group-level authorizations.

So as not to reinvent the wheel, while researching good solutions to this problem, we came across Zanzibar, the global authorization system used at Google for handling authorization for hundreds of its services and products, including YouTube, Drive, Calendar, Cloud, and Maps.
Zanzibar’s Architecture can be seen as below.

zanzibar-arch
Source: Zanzibar whitepaper

The white paper claims that it can scale to trillions of access control lists and millions of authorization requests per second to support services used by billions of people. It has maintained a 95th-percentile latency of fewer than 10 milliseconds and availability of greater than 99.999% over 3 years of production use. This piqued our interest, so we started to look for inspiration, and we came across Permify, an open-source authorization service based on Google’s Zanzibar. We quickly did a proof of concept and decided to use it. Let’s look at the features/benefits of Permify –

  • No complex join queries to validate if a user has permission or not.
  • Ability to generate the relations based on our data i.e., source of truth. Reproducible.
  • Decoupled with the service, all authorizations can be handled in middleware.
  • Highly Scalable. It can be deployed on Docker containers and Kubernetes. For setups and deployments, read installation docs

Implementation

Setup

After a quick proof of concept and trial, we decided to host our own Permify instance and use it as the central Authorization Service. This allowed us to be in control and scale it as and when needed.

By default, Permify stores authorization data and permissions in memory. We can also choose a database to store this data, and they recommend PostgreSQL for the same. Permify can also be scaled horizontally for high availability and to handle more load. In our case, we went ahead with the PostgreSQL setup.

permify-arch
Source: Permify Docs

Schema Model

Permify is designed as a ReBAC(Relationship-based access control) solution. It extends the traditional RBAC. This means we can define a user has specific permissions because of their relation to other entities. For example, if a workspace contains multiple projects, then the admin of a workspace is also the admin of the projects inside it.

The entities, their relations, and roles can all be modeled using Permify’s Domain Specific Language (DSL) on an IDE or their playground.
In our case, our models looked somewhat like this:

permify-1-1

Now, just by reading these models, one can clearly understand a lot of information. For example, it says that

  • All the users who have edit access or are a member of the workspace have read permission on it
  • A task_list can be edited by either the owner of the list or by the user who has edit permissions on its parent (i.e – the workspace it belongs to)
  • The member of a project can be a user or all the members of a workspace

and so on

Permify allows various relationships using its DSL. For in-depth details around schema design, please refer documentation
Once the models are defined, we can generate authorization data like this:

permify-2-2

The model can help you answer a wide variety of questions like:

  • Does User 2 have permission to read task_list 5?
  • Who are the members of Project 3?
  • Who can edit task_list 2?
  • What all workspaces does the User 2 have access to?
  • Who’s the parent of Organization 5?

and so on.

Permify also provides the SDK in multiple languages to interact with Permify server. Using SDK it allows us build the access control policies and perform access checks. In our case, we were using the Go SDK and wrote a few wrapper functions on top of it for ease of use.
Below is a snippet to demonstrate this.

permify-mk-3

The above function can be used in the handler to verify if the user has write access to a task list.

permify-mk-4

Any authorization system is a central component, and it is often a performance bottleneck. To keep things efficient, the Permify SDK uses gRPC calls to talk to the Permify server. Permify server also offers HTTP endpoints for use cases such as writing small scripts for batch edits or corrections.
Permify allowed us to quickly build a robust, scalable, and configurable authorization service, and we recommend that you try it out.

If your organization needs help in building and scaling your systems, reach out to us here.

Mohit Kumar
Mohit Kumar