Auth0 and Permit Integration
Introduction
Auth0 is one of the leading authentication platforms for developers, and Permit.io is the leading authorization platform for developers. By integrating Auth0 with Permit, you can easily add secure, feature-rich, and scalable authentication and authorization systems to your application.
You can see more information about RBAC Permit and Auth0 in this great blog post by Gabriel L. Manor
What you’ll build
In this tutorial, you’ll learn how to integrate Auth0 with Permit. We will create a WebApp that uses Auth0 for secure authentication and Permit.io for easily manageable authorization, allowing us to change our application's permissions on the fly.
Prerequisites
- Auth0 account
- Permit account
- An application that uses Auth0 for authentication; in this example, we will use a NextJS app Check out the demo explanation for more details.
Getting Started
1. Setting up Permit
1.1. Setup roles in Permit
To sync users and roles from Auth0 to Permit, we must recreate identical roles in Permit.
If you don't use Auth0's roles, you can skip this step and create your roles according to your application's needs.
Setting up roles and resources can be done through the Permit dashboard, by using the Permit API, or with one of our SDKs.
1.2. Create the right resources in Permit
Create a corresponding resource in Permit for each of your application's resources. For instance, if you have a user management section in your app, you will need a user
resource with create
, delete
, and get
actions. If you have a task management section, you will need a task
resource with create
, delete
, get
, and check
actions.
Setting up resources can be done through the Permit dashboard, by using the Permit API, or with one of our SDKs.
1.3. Set up the right permissions in Permit.io:
For each role you created in step 1.1, you will need to set up the right permissions.
Check each action you want to allow for each resource in each role.
As noted earlier, these permissions can be set up through either the Permit policy editor or by using the Permit API.
2. Integrate your own application
2.1. Auth0
As we want to use Auth0 for authentication, we will use the Auth0 SDK to get each user's information and access token. You will need to add in your FE code a check to see if the user is logged in and redirect them to the login page if they are not.
In our demo application it look like this:
We have the Auth0 useUser
at index.tsx
that will give us the user's information, and the userProvider
in _app.tsx
that redirects the user to the login page if they have not logged in.
// index.tsx
//...
const { user, isLoading } = useUser();
//...
// _app.tsx
//...
<UserProvider>
<Component {...pageProps} />
</UserProvider>
//...
On your backend side, you will need to add code that will check the validity of the Auth0 JWT and retrieve user information from it.
In our demo app, we used an Auth0 and NextJs integration to achieve this with very few lines of code (as you can see in [auth0].ts
and the getSession in tasks.ts
).
// pages/api/auth/[...auth0].ts
import { handleAuth } from "@auth0/nextjs-auth0";
export default handleAuth();
// pages/api/tasks.ts
import { getSession } from "@auth0/nextjs-auth0";
//...
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession(req, res); // if the user is not logged in, session will be null
if (!session?.user) {
res.status(401).json({ message: 'unauthorized' });
return;
}
//...
2.2. Permit
As we want to use Permit for authorization, we will use the Permit SDK to get the user's permissions and check if they have the right ones.
Authorization is only done on the backend side, so we will use the permit
checks before each action. You can add them to your CRUD APIs or as a middleware for the entire API.
All you need to do is add the permit.check
function before each action you want to protect.
permit.check(
session?.user.sub, // the user's id
"delete", // the action name
"task" // the resource name
);
In the demo app, you can see it at pages/api/tasks.ts
as a middleware that manages all task CRUD requests.
// pages/api/tasks.ts
import { Permit } from "permitio";
export const permit = new Permit({
pdp: "https://cloudpdp.api.permit.io",
token: process.env.PERMIT_SDK_TOKEN,
});
export default withApiAuthRequired(async function handler(
req: NextApiRequest,
res: NextApiResponse<Task | Task[] | Response>
) {
// Auth0 is checking if the user is logged in (who the user is)
const session = await getSession(req, res);
if (!session?.user) {
res.status(401).json({ message: "unauthorized" });
return;
}
// Permit is checking if the user has the right permissions (what the user can do)
const isAllowedForOperation = await permit.check(
(session?.user?.sub as string) || "", // the user identifier (Permit user id / or Permit user key, in this example we set the Auth0 user id as the key)
req.method?.toLowerCase() as string, // the action (can be: get, post, delete)
"task" // our resource key
);
if (!isAllowedForOperation) {
res.status(403).json({ message: "forbidden" });
return;
}
//... handle the request
});
3. The integration
Now that we have the user's information and permissions, we can integrate them. We need to make sure that Permit is synced with Auth0 so that each user who logs in will have the right roles and permissions.
3.1. Sync Auth0 with Permit
First, we need to make sure each user that logs into our app is synced with Permit.
This can be done by adding a postLogin
function that will be called after each login.
This function will make sure that the users and roles are synced with Permit. You can check the NextJS demo app for the full code.
If you are using Auth0 roles you will need to add them to the user's token in order to easily sync them with Permit. This can be done following this Auth0 guide.
Here's a quick recap on how this can be done (Please use the Auth0 guide for a more detailed description):
- Go to your Auth0 dashboard > Applications > API.
- Create an API if you don't have one.
- Click on the three dots on the right of your API and click on settings.
- Scroll down to
RBAC Settings
and enableAdd Permissions in the Access Token
. - Click on
Save Changes
.
In short, you need to navigate to the Auth0 dashboard and create a new rule https://manage.auth0.com/dashboard/[your-region]/[your-tenant-name]/rules. The Auth0 rule that you need to add will look like this:
function (user, context, callback) {
const namespace = 'my_app_name';
const assignedRoles = (context.authorization || {}).roles;
let idTokenClaims = context.idToken || {};
let accessTokenClaims = context.accessToken || {};
idTokenClaims[`${namespace}/roles`] = assignedRoles;
accessTokenClaims[`${namespace}/roles`] = assignedRoles;
context.idToken = idTokenClaims;
context.accessToken = accessTokenClaims;
callback(null, user, context);
}
Now you can see that the session.user object from Auth0 has my_app_name/roles
section with our roles (of course, you can set <my_app_name> to whatever you want):
auth0User {
'my_app_name/roles': [ 'admin' ],
nickname: 'test',
name: 'test@permit.io',
picture: 'https://s.gravatar.com/avatar/test.png',
updated_at: '2023-07-05T10:56:35.831Z',
email: 'test@permit.io',
email_verified: false,
sub: 'auth0|64a139da377fefdcbxxxxxxx',
sid: 'I9xntALjR3M37Iw62Lgc3xxxxxxxxxx'
}
Then when a user logs into our app, we can also sync their roles with Permit by using permit.api.syncUser
function and then assign the roles to the user using permit.api.assignRole
function.
// sync the user with Permit
const userObj = {
key: auth0User.sub, // the user's key needs to be unique, you can use the Auth0 user id, we will use it to perform the permit checks.
email: auth0User.email,
first_name: auth0User.name,
attributes: {}, // you can add more attributes here, mostly used for ABAC
};
const permitUser = await permit.api.syncUser(userObj);
auth0User.roles.map(async (role) => {
// assign the roles to the user
const role = await permit.api.assignRole(
permitUserObj.key, // the user's key
role, // the role name
[tenantKey] // the tenant key set `default` if you don't use multi-tenancy
);
});
Now if you assign a role to a user (Either in Auth0 or in Permit), the user will have the correct roles and permissions. (Make sure you use the exact same role names in both Auth0 and Permit - they are case-sensitive!)
Check out the postLogin page in this NextJS example to see a working example.
3.2. Removing a user
If you want to remove a user from your app, make sure you remove the user from both Auth0 and Permit. You can do this by adding the following code to your remove user function:
const removedUser = await permit.api.deleteUser(permitUserObj.key);
4. Let's test it!
Create a new user in Auth0 and assign them with the manager
role.
Then, log in to the app with the new user.
You should be able to create tasks, but not delete them.
Now go to the Permit policy editor and add the delete action to the manager
role and save the policy.
Otherwise, you can go to the Permit user management screen and add the admin
role to the logged-in user.
You should now be able to delete tasks immediately without logging out and logging in again.
If you remove a role in Permit but the user still has the role in Auth0, our code will reassign the role to the user. You can fix this by adding the following code at the begging of the postLogin function:
const session = await getSession(req, res);
// check if user exists in permit
const user = await permit.api.getUser(session?.user?.sub);
if (user) {
console.log("user exists");
redirect(res, 302, "/");
return { props: {} };
}