Contents

Architecting a Scalable Permissions Service for SaaS Web Applications

Architecting a Scalable Permissions Service for SaaS Web Applications

Design your Role-based Access Control service to manage permissions in your SaaS web application.

https://cdn-images-1.medium.com/max/800/1*xp6uPLbTCgSLrwt9GlBarw.jpeg

Source: Pixabay

This post can serve as a reference document highlighting some of the best practices in architecting your Role-Based Access Control Service.

This article is a follow-up from my previous write-upArchitecting a Scalable Software as a Service. I only briefly mentioned the Role-Based Access Control (RBAC) in my earlier article. However, we will zoom in on the design details of one of the core components in our SaaS design: the RBAC Service, in this post.

RBAC enables a granular way to manage the permissions of users and organizations. Our core domain models areUser,Organization,Role, andPermission. We will dive into each domain model in ourDatabase Schemasection.

Why use an RBAC Service?

RBAC is a crucial component in your Software As a Service (SaaS) web application. It determines the privileges that your logged-in user has in your SaaS. It checks if the logged-in user can perform specific actions like delete another user or access a set of reporting dashboards. We can use this to handle the features available to different user tiers in your SaaS web application. For example, trial users can only use feature A. In contrast, premium users can use features A, B, and C. We can also use them to determine user privileges like read-only and admin access.

If you are using micro-frontends, the RBAC service is also flexible enough to be reused across the micro-frontends that make up your SaaS. Using RBAC can extend your SaaS by adding micro-frontends without having to worry about re-creating the permissions functionality as you grow or scale your SaaS.

  • Flexibility. It can accommodate growing users, changing the permissions of a single user or a group of users, or assigning organizations to different subscription tiers.
  • Scalability. It is horizontally scalable. We can add more instances of the service as needed to accommodate growing users.
  • Separation of concerns. RBAC service will be running independently. The web application or micro-frontend that makes up our SaaS will call the RBAC API.

Requirements

Functional requirements

  • We can give users access to the features that are available to them.
  • We can group users into organizations. Our RBAC should handle a company or organization-wide subscription.
  • Users can switch between organizations.
  • Administrators should be able to manage users, roles, and permissions in their organizations.

Non-functional requirements

  • The service should be horizontally scalable.
  • The service should be reusable across multiple web applications.
  • The service can handle read-heavy requests. Expect more reads than writes, especially when querying the permissions available for a user. Our service will always try to answer the question: What are the available permissions for the logged-in user? The writes will happen in the admin panel when we update user permissions or add new users.

High-level design

https://cdn-images-1.medium.com/max/800/1*vtuR8eYna0n_Bw9_nEEPRA.png RBAC service in a SaaS web app. Diagram by the author.

The Saas web application consumes RBAC via a RESTful API or gRPC call.

High-level sequence

The sequence below shows how the RBAC service with a SaaS Web App handles user authorization. We are assuming that we have an authenticated user in this sequence.

https://cdn-images-1.medium.com/max/800/1*Ga7HkxuH4NZGFdQFf6fXBA.png Permissions service sequence. Diagram by the author.

The primary purpose of our RBAC service is to provide the roles and permission information to the Web App. Once the Web App has the permission value, it is still responsible for deciding what to do with the value. Based on the roles and permissions, the web app will check if it could grant or revoke access and show or hide certain features to the user.


Database Schema

User, organization, role, and permission

The main tables that we will use are for storing users, organizations, roles, and permissions.

https://cdn-images-1.medium.com/max/800/1*unkGRSu2IfRpUTePNZqwRw.png RBAC tables of foreign keys and relationships. Diagram by the author

  • User— authorized users. A user should belong to at least one organization.
  • Organization— We will group users in an organization to assign roles to multiple users at a time. Instead of having to assign generic roles like “freeTier” individually to each user.
  • Role— We will group permissions in a role to assign multiple permissions to users and organizations. For example, a role could be “admin” for users with administrator privileges, or “freeTier” for users that belong to an organization that has a free tier subscription. We can assign a role to users and organizations. A role can have multiple permissions.
  • Permission— Determines the privileges that a role is allowed to perform. For example, permission value can be “canViewReportingDashboard” or “canRemoveUser”. We should assign permissions to a role. The SaaS web application will use the permission value to decide what privileges the logged-in user has.

APIs

Here are some of the core APIs that we will need in our SaaS. We will not cover the obvious APIs likecreateUserorcreateOrganization. I have written the APIs below with pseudocode for illustration purposes only. They do not represent the actual code or pattern that you should use.

Get Users by Organization

This API returns a list of users for an Organization. We will use this API in the admin panel when we want to browse through a list of users in an organization to update their permissions.

getUsersByOrganization(orgId) -> [{username: 'ardydedase', id: 1}, .....]

Get Roles by User

This API returns the roles assigned to a user. Useful for generalizing what type of privileges the user has. We will use this API in the admin panel when we want to assign permissions to specific roles.

getRolesByUser(userId) -> ['admin', 'premiumAccess']

We can provide users with less granular privilege access with roles. For example, the web app can make the admin features available if it knows that the logged-in user has an admin role.

roles = getRolesByUser(userId)
if ROLES.ADMIN in roles { 
  showAdminFeatures()
}

Get Permissions By User

This API returns a list of permissions available to a user. We will use the list of permissions to determine the user’s privileges.

getPermissionsByUser(userId) -> ['canManageUser', 'canAccessBillingDashboard']

We can use permissions to know if a user has a specific privilege or more granular access to a feature. For example, the web app can show the “remove user” functionality if it knows that the logged-in user has a “manage user” permission.

permissions = getPermissionsByUser(userId)
if permissions.CAN_MANAGE_USER in permissions { 
  showRemoveUserFeature()
}

We can also build a wrapper around thegetPermissionsByUserAPI and use the following logic:

View Code on GitHub Gist

Code snippet by the author.

View Code on GitHub Gist

Code snippet by the author.

Get Organizations By User

Returns the list of organizations a user belongs to.

getOrganizationsByUser(userId)

We will use this list of organizations to show users the option to switch between organizations that they are a part of.

organizations = getOrganizationsByUser(userId)
if organizations.length > 0 { 
  displayOrganizationsDropdown(organizations)
} else { 
  showErrorMessage()
}

Add User to an Organization

We should be able to add a user to an organization using the admin panel. Users should belong to an organization so we could determine their organization-level permissions like:freeTrialorpremium.

addUserToOrganization(userId, organizationId)

Assign Role to User or Organization

We should have the option to assign roles individually to a user or an organization.

assignRoleToUser(roleId, userId)
assignRoleToOrganization(roleId, organizationId)

Add or remove permission to Role.

We should be able to assign permission to a role.

addPermissionToRole(permissionId, roleId)
removePermissionFromRole(permissionId, roleId)

Reliability and redundancy

Our SaaS web applications rely on the RBAC service to know the logged-in user’s permissions. Depending on how the SaaS web application handles RBAC failures, it could be potentially unusable if our RBAC service is not available. We want to remove any single point of failure in our service.

Multiple RBAC instances

Our service should have more than one instance of the RBAC service running on production. Where some instances are serving live traffic, and others are on standby. The healthy back-up instance will gracefully take over to accept live traffic if something goes wrong with the live instance.

https://cdn-images-1.medium.com/max/800/1*iHoDRFk1aHa21DS8Of2lzA.png RBAC with multiple instances for redundancy. Diagram by the author.

Master-slave replication

We will use the master-slave replication strategy for our database. Where We will use the master for writes; to update our permissions data. Every time our admin frontend sends an update request to RBAC, it will go to our master. However, all the RBAC reads from Saas web applications will be routed to the slave replicas. Having more slave replicas for reading makes sense here because our service is read-heavy. We also have multiple micro frontends in our SaaS that rely on the RBAC to retrieve user permissions.

https://cdn-images-1.medium.com/max/800/1*EWjb9oFaBkNuLMccksy4fg.png Master-slave replication in the RBAC service database. Diagram by the author.

Load balancers

We will need load balancers in front of our RBAC service instances and our Database slave replicas. Load balancers enable our service to distribute traffic across service and database instances. We can add more instances behind our load balancer to horizontally scale as needed in the future.

https://cdn-images-1.medium.com/max/800/1*5FnD8sLbTHhi6eqi87yUOw.png Load balancers in the RBAC service. Diagram by the author.

Further optimizations

Here are some features we can consider as we further scale our RBAC service.

Caching

Caching will help reduce the load to our servers. We can introduce caching for “hot” permissions data. The Least Recently Used (LRU) policy is most appropriate in this case. We will evict the least used permissions data and serve the most used ones. Our cache services can have their load balancer as well.

https://cdn-images-1.medium.com/max/800/1*P2vouoO1c7wdb4ALSG1uUw.png Diagram by the author.

RBAC clears the associated cached data when it detects that that piece of data gets updated from the admin UI.

You have the option to offload the caching responsibilities to the SaaS web app and RBAC admin UI. That will give your clients control over their own RBAC cache.

Dedicated read and write services

Most SaaS won’t probably reach this stage. However, we could further optimize our RBAC design if we have hypothetically increased our writes, i.e., we have introduced multiple SaaS tenants that require their own dedicated RBAC admins. Here, we have separated the services into two; one for reading and the other for writing.

https://cdn-images-1.medium.com/max/800/1*mNY-CKQDzQoRSaKk8ik82g.png Diagram by the author.

Implementation tips

The more granular our permissions are, the more traffic we are expecting in our service. I would use compiled languages like Java, Golang, or C# to write the application layer of the service. I’m a bit biased with using Golang, though, since that’s what I’ve been learning and using more recently.

Our database schema is pretty much fixed, and we don’t expect it to change. We also have a lot of relationships between our tables in our schema. A good old RDBMS like MySQL or Postgres will be great for our datastore.

We will then use either Memcached or Redis for our caching.

Conclusion

If you start a new product without any users, use the RBAC design’s bare minimum — consider theHigh-level strategiesandAPIs. Then revisit your architecture when you need to scale further.

On the other hand, if you already have a SaaS with existing users and are expected to continue growing, consider the guidelines until theRedundancy and reliabilitysection.

Implement theFurther optimizationssection if you need them down the line**.**

Check out my other posts about Software Architecture: