11 min

Setting up Roles and Permissions in Nextjs

Setting up Roles and Permissions in Nextjs

Role based access control and Multi-tenancy in Nextjs and React

In this tutorial we will go over an implementation of how to implement Role Based Access Control, commonly referred to as RBAC, in a Nextjs and React app.

To help us with this we can use the most popular RBAC library in the ecosystem, Casl

How Roles based Access Control Works

RBAC is a common and critical feature in modern SAAS apps. It allows Organizations to assign certain permissions to certain roles and assign those roles to specific users.

This allows giving permissions categorically rather than implementing permissions on a per user basis.

An example of this could be and Admin role having permissions to manage an organizations payment information while a Member role would not.

The Admin role user would be able to access the part of the application that can change payment information while the Member role user can't access that part of the app.

Multi Tenancy.

In modern SAAS apps Multi-tenancy is usually used along side RBAC.

Multi-tenancy essentially allows multiple tenants inside of one application, with each tenant having its own data and settings.

An example of this could be an app with multiple Organizations and Teams.

Multi-tenancy and RBAC working together allows a single user to join different teams or orgs with unique roles in each team or org.

Real World Implementation

Here we can look at the implementation that we use in SAAS starterkit.

In Saas Kit we have Orgs we use for multi-tenancy. Each org is isolated from another and has its own data, subscription and settings.



Can be other setups or names such as teams or projects.

Role hierarchy

Owners > admins > users

Only one owner per org.

Database setup to work with roles and Orgs

Dataschema for Orgs, Roles, Invite and Users

Roles flow

Owner creates Org.

Owner sends invite to Admin or Member

Admin accepts invite, lands on org-invite page with token. Token used to validate invite.

Data from invite used to create role

Delete invite.

Admin now has access to org.

Owner clicks on remove admin: Role deleted from roles table, admin no longer has access to app.

Can use org_id and user_id to get role for specific user on specific org.

Lets look at each part of the flow in detail with code.


Now that we have defined our roles we need a way to manage permissions. One way we can do this is manually writing many if(role === “Admin”) statements in our code, but that is very messy and error prone.

We can instead use a library that allows us to centrally manage and define our permissions for each role.

Casl is currently the most popular npm library for managing RBAC (role based access control)

Defining Permissions

Permissions follow a very predictable pattern. We define permissions based on actions on a subject

Actions will be CRUD operations and Subject will be a database model.

For example Actions: “READ”, Subject: “Todos”, here we give permission to read the todos model or table.

This is very similar to SQL row level security pattern

Casl Ability


Casl and Role Context Caslcontext, wrap layout. Role context.

Guarding UI


Guarding Server Actions and check permission function.

1export const GetBillingUrl = async ({ customer_id }): Promise<string> => {
2 let portalSession: Stripe.BillingPortal.Session;
3 const customer = customer_id;
5 const origin = configuration.url;
7 try {
8 portalSession = await stripe.billingPortal.sessions.create({
9 customer,
10 return_url: `/dashboard`
11 });
12 } catch (err) {
13 throw err;
14 }
16 return portalSession.url;
19// component.tsx
20// redirect after fetching the stripe signed billing URL
22const handleSubscription = async () => {
23 const redirectUrl = await GetBillingUrl({ customer_id });
24 router.push(redirectUrl);