How to Add RBAC Authorization to Auth0
- Share:
Authentication SaaS is everywhere
The authentication and identity management world has changed dramatically over the last decade. As applications migrate to authentication-as-a-service providers such as Auth0, internal databases, and password hashes become a thing of the past. With these solutions, you can easily implement authentication into your app while taking advantage of great features such as federated identity, social logins, MFA, identity verification, and more. For a modern web application that needs to verify who its users are, you don’t need anything but to open an Auth0 account. After a short configuration setup, you can integrate a rich authentication feature set into your application with single lines of code.
Authentication alone is not enough
One of the access control challenges with Auth0 is the authorization phase. While Auth0 excels in verifying who the users are, they are limited in telling you what they can do. Having any authenticated user be able to perform any action poses great risks, as we recently saw in the Bing hack incident.
Authorization is a critical phase in monitoring user access and making sure only the right people have the right access to the right resources within your application.
The solution? Authentication SaaS + Authorization SaaS
Permit.io is a cloud service that provides a low-code tool for implementing and managing authorization. By configuring identities, resources, and actions, you can create policies and enforce them in your application. Basically the same concept as Auth0, but for authorization.
Combining the two services gives your application a complete cycle of access control and helps you ensure your authenticated users can do only what they should be allowed to.
In this article, we will review the steps of implementing Auth0 together with Permit.io.
Using the Auth0 getting started guide for Next.JS, we will demonstrate how you can completely redefine the permission model of our application with nothing but a few lines of code - Limiting your Auth0 authenticated users to only perform the actions they are allowed within your app.
By the end of this article, you should have a solid understanding of how Permit.io can be implemented into your app in your favorite coding language using any of our SDKs.
Before we begin, let’s dive a little deeper into how these two solutions work together.
Authentication vs. Authorization - Auth0 vs. Permit.io
In an application that uses Auth0 for authentication, the authentication process is typically handled client side. This means that when a user attempts to access a protected resource, such as a restricted page or API endpoint, the client-side code sends a request to Auth0 to authenticate the user. Auth0 then returns a token that the client-side code can use to authenticate subsequent requests to the server.
Authorization, on the other hand, is typically performed server-side. This is because authorization involves enforcing access policies specific to the application and its resources. In order to enforce these policies, the server-side code needs access to the user's identity, which is typically included in the authentication token.
When using Permit.io, the authentication process remains unchanged. However, when a user attempts to access a protected resource, the server-side code sends a request to Permit.io, which checks whether the user is authorized to perform the requested action.
Permit.io uses the identity information provided by Auth0 to determine the user's permissions and enforce access policies. This means that the application can easily define fine-grained access controls based on user roles, permissions, and other attributes and enforce those controls in a central location.
Demo Application
To demonstrate the relationship between authentication and authorization, we created a simple to-do application, with authentication already implemented into it based on the Auth0 Next.JS getting started guide. Let’s clone it and run it on your local environment.
Note: while this article does not require you to have Next.JS knowledge, it requires an Auth0 account with a basic configuration, and Node.JS installed in your local or remote environment.
Open the terminal and type the following command in your desired development folder
npx create-next-app@latest permit-auth0-todo --use-npm --example https://github.com/permitio/permit-next-todo-starter auth0-tutorial && cd permit-auth0-todo
Paste your Auth0 keys in the .env.local file as listed below. To get your own keys in the desired format, use this link while logged in to your Auth0 account
AUTH0_SECRET='<auth0_secret>' AUTH0_BASE_URL='http://localhost:3000' AUTH0_ISSUER_BASE_URL='<auth0_issuer_base_url>' AUTH0_CLIENT_ID=<auth0_client_id> AUTH0_CLIENT_SECRET='<auth0_client_secret>'
Run the following command from the application folder
npm run dev
In your browser, go to localhost:3000, log in with one of your Auth0 users, and see the tasks list on the screen.
We now have an app with basic authentication rules set up and running. Now, let’s see how we can add a granular authorization layer into this app using Permit.
Building Permissions - Roles and Attributes
The first step when building authorization is designing your permission model.
The basic building blocks of every permission model are Identity, Resource, and Action. The relationship between these three elements defines access control policies (Identity can perform Action on a Resource).
Identity
The basic identities in our application are those provided by the Auth0 Authentication process. To create a permission model, we would need to translate those identities into roles (which will allow us to implement Role Based access control) or add attributes onto them (for Attribute Based access control).
Resource
As our application is quite simple, we have only one resource we want to manage access to - the tasks. Similar to identities, resources also have attributes that identify them. For example, tasks have an isCompleted attribute that identifies it as a completed task or an owner attribute that belongs to a particular user who created it.
Action
This part of the policy is the glue between the Identity and the resource. By understanding what actions identities can perform on resources, we can enforce our access control policies and decide who can do what on which resource within the app.
The full picture
We can describe the relationships between our identities, resources, and actions by putting them all on a table. By creating a separate table for each role, we can see exactly which actions this role can perform on our resource.
Here is an example of the permission model where Editor that can create tasks but not delete them while Moderator is not able to create tasks but can update them:
Admin | |||||
---|---|---|---|---|---|
Action 👉 Resource 👇 | Post | Put | Patch | Delete | Get |
Task | V | V | V | V | V |
Editor | |||||
Task | V | V | |||
Moderator | |||||
Task | V | V | |||
Roles… |
Building Permissions
Now that we clearly know how our permissions should look, it’s time to log in to app.permit.io and create it.
Go to the Policy page and click Create > Resource
Create a Resource, and assign it with Actions:
Next, we will create our Roles:
Go to the Policy page and click Create > Role
Add the following roles
To make things easier, make sure to mirror the roles you have set up in Auth0. In your production application, you can think of the right way of maintaining one source of truth for roles in the application.
In the Auth0 admin application, go to User Management > Roles and add the following.
For each role, we would like to add a user assigned to this role. In Auth0, go to User Management > Users and add the following. Pay attention that you assigned the relevant role to each user.
With the roles, resources, and actions you just created, we have an outline of the policy table configuration. Let’s go back to Permit.io policy editor screen and check the following boxes to adopt the policy for our needs.
Manage Identities - Sync Users
Having defined permissions in Permit.io, it is time to connect the dots between our authentication and authorization services and make Permit.io aware of our application users and roles managed by Auth0.
Auth0 provides the option to write actions that run as a result of user actions. We will use the post-login
action to sync the users to Permit.io. This way, we can ensure every user login to our applications is updated with their roles in Permit.io. You can read more about actions here.
Follow the following steps to enable sync users action in your Auth0 account:
In the Permit.io application, go to Project and copy the API key.
In your Auth0 account, go to
Actions > Library
, click the+ Build Action
button, and create the following action.In the online IDE opened, click the key icon on the left side, and paste your copied Permit SDK key.
In the same left bar, click the package icon to install permitio sdk in Auth0
Paste the following code that will sync users while they are login to the application.
const { Permit } = require('permitio'); exports.onExecutePostLogin = async (event, api) => { const permit = new Permit({ pdp: 'https://cloudpdp.api.permit.io', token: event.secrets.permit, }); const { email, given_name: first_name, family_name: last_name } = event.user; const user = await permit.api.syncUser({ key: email, email, first_name, last_name, attributes: {}, }); for (let role of event.authorization.roles) { await permit.api.assignRole({ role, tenant: "default", user: email }); } };
Click on the Deploy button, and we're done. All the users will sync to Permit.io after they log in.
To test the user synchronization to Permit.io, go back to the browser and log out/log in with one of your users. Then, go to the Permit.io Users
screen and see the new user appear at the table with the relevant role.
Sync Troubleshooting
If your users are not syncing between Auth0 and Permit.io at this point, no worries this can be tricky 😉, here are some suggestions to help you debug the sync functionality:
- Test the sync action in the Auth0 UI and verify that it successfully syncs users to Permit.io. Ensure that all the attributes in the API call exists and valid in Permit.io.
- Confirm that the
Login/Post Login
action is being called as a callback after login. You can check this by auditing the API logs in Permit.io underSettings > API Logs
. - If the previous steps do not resolve the issue, you can explore additional methods to synchronize users to Permit.io for more advanced use cases.
Enforce Permissions in the Application
At this point, we have everything configured in both Auth0 and Permit.io. The only thing left is adding enforcement points to our APIs. As we explained before, the enforcement will occur at the API route where we want to allow or deny a particular user to perform an action.
Grab the SDK key again and paste it into the .env.local file in the following format.
PERMIT_SDK_TOKEN=<permit_sdk_token>
Run the following command from the terminal to install the Permit.io SDK into the project.
npm i permitio –save
In the tasks.ts file, paste the following code just below the import.
import { Permit } from 'permitio'; const permit = new Permit({ pdp: 'https://cloudpdp.api.permit.io', token: process.env.PERMIT_SDK_TOKEN, });
Find the beginning of the handler and paste the following code. This code takes the identity that Auth0 provides and sends it to Permit.io to verify if the user is allowed to perform the operation.
const isAllowedForOperation = await permit.check( session?.user?.email as string || '', req.method?.toLowerCase() as string, 'Task' );
At this point, we are only left to return an error if the user is not allowed.
if (!isAllowedForOperation) { res.status(403).json({ message: 'forbidden' }); return; }
After implementation, let’s go back to your application in the browser and see it in action. Go to the browser, log out with the current user, and log in with a different role user to see how the application responds differently to operations by user roles.
What Next?
Congratulations, you successfully implemented a fully functional permission model to an application and created fine-grained access control that enforces policies on what users can do.
As you saw in this post, Permit.io gives you a full-stack authorization management solution, so you don’t have to build one yourself. Permit.io provides SDKs for many programming languages, so you can investigate them and find the relevant solution for your particular application.
Permissions and authorization are essential to success in access control. Still, the model we created here is just the beginning of a good permissions model you can implement in the application. For example, what if you want to differentiate the same resource types by their attributes? What if you want a more granular level of control than user roles but user-detailed identity? For that and more, we recommend you to continue reading our learning materials, such as the difference between RBAC and ABAC, adding ABAC to your application with Permit, and more.
Written by
Gabriel L. Manor
Full-Stack Software Technical Leader | Security, JavaScript, DevRel, OPA | Writer and Public Speaker