Auth0’s RBAC (Role-Based Access Control) isn’t really about roles as much as it’s about groups of permissions.
Let’s see this in action. Imagine we have a simple application where users can view and edit articles. We want to give "Editors" the ability to edit, but "Viewers" only to view.
First, we need to define our permissions in Auth0. Navigate to "Security" -> "Roles" in your Auth0 dashboard. Click "Create Role" and name it Editor. Now, click on the Editor role and select the "Permissions" tab. Click "Add Permissions" and create two new permissions: view:article and edit:article.
Next, create another role named Viewer. Add only the view:article permission to this role.
Now, let’s assign these roles to users. When a user logs in, their JWT (JSON Web Token) will contain custom claims. We need to configure Auth0 to include these role assignments in the JWT. Go to "Security" -> "Roles" and click on the Editor role. Under "Role Assignment" -> "Add User", search for and add a user. Do the same for the Viewer role.
To make sure these roles are actually sent in the token, we need to configure a "Rule" or a "Hook". Let’s use a Rule. Go to "Workflow" -> "Rules". Click "Create Rule". Choose the "Empty Rule" template.
Here’s the JavaScript code for the rule:
function (user, context, callback) {
// Get user roles from Auth0
const assignedRoles = context.authorization.roles;
// Map roles to permissions
const permissions = [];
assignedRoles.forEach(role => {
if (role === 'rol_example_editor_id') { // Replace with your actual Editor role ID
permissions.push('view:article');
permissions.push('edit:article');
} else if (role === 'rol_example_viewer_id') { // Replace with your actual Viewer role ID
permissions.push('view:article');
}
});
// Add permissions to the token as a custom claim
context.idToken['https://your-app-domain.com/roles'] = ['Editor']; // Optional: add role names
context.idToken['https://your-app-domain.com/permissions'] = permissions;
callback(null, user, context);
}
Important: You’ll need to replace 'rol_example_editor_id' and 'rol_example_viewer_id' with the actual IDs of your roles in Auth0. You can find these IDs in the URL when you’re viewing the role details in the Auth0 dashboard. The https://your-app-domain.com/ part should also be replaced with your application’s domain.
This rule iterates through the roles assigned to the user. If the role ID matches an editor role, it adds view:article and edit:article to a permissions array. If it matches a viewer role, it adds only view:article. Finally, it adds this permissions array as a custom claim (https://your-app-domain.com/permissions) to the user’s ID token. You can also add role names for easier debugging or display.
In your application’s backend (or frontend, if appropriate), you would then inspect the idToken (or accessToken, depending on your configuration) for the https://your-app-domain.com/permissions claim.
For example, in Node.js with express, after verifying the JWT:
const jwt = require('express-jwt');
const jwksClient = require('jwks-rsa');
const checkJwt = jwt({
secret: jwksClient.expressJwtSecret({
cache: true,
rateLimit: true,
jwksUri: 'https://YOUR_AUTH0_DOMAIN.auth0.com/.well-known/jwks.json'
}),
audience: 'YOUR_API_IDENTIFIER', // Or your app's client ID if using ID tokens
issuer: 'https://YOUR_AUTH0_DOMAIN.auth0.com/',
algorithms: ['RS256']
});
app.get('/articles/:id', checkJwt, (req, res) => {
const requiredPermissions = ['view:article'];
const userPermissions = req.user['https://your-app-domain.com/permissions'] || [];
const hasPermission = requiredPermissions.every(permission => userPermissions.includes(permission));
if (hasPermission) {
// Fetch and return article
res.send(`Article content for ${req.params.id}`);
} else {
res.status(403).send('Forbidden: Missing view:article permission.');
}
});
app.put('/articles/:id', checkJwt, (req, res) => {
const requiredPermissions = ['edit:article'];
const userPermissions = req.user['https://your-app-domain.com/permissions'] || [];
const hasPermission = requiredPermissions.every(permission => userPermissions.includes(permission));
if (hasPermission) {
// Update and return article
res.send(`Article ${req.params.id} updated.`);
} else {
res.status(403).send('Forbidden: Missing edit:article permission.');
}
});
This setup allows you to define granular permissions and group them into roles, which are then dynamically added to user tokens. The actual authorization logic resides in your application code, checking for the presence of specific permissions within the token’s claims.
The most surprising thing about Auth0’s RBAC implementation is that it doesn’t enforce permissions directly; it merely augments the JWT with information about the user’s roles and permissions. Your application is entirely responsible for reading these claims and making authorization decisions.
If you want to manage permissions at a more granular level without hardcoding them into rules, explore Auth0’s "Permissions" feature within the "Roles" section. This allows you to define permissions as distinct entities and assign them to roles, which is a more scalable approach than the manual string-based approach shown in the rule.