I was recently asked to look at a javascript function that would do something different for each user role. However it was quite apparent it would get out of hand very quickly. My proposal to the client was to restructure their role based approach into a permissions based one. in this blog post I'll explain my approach to transitioning from a role based system to a permission based system.
Transitioning from Role-Based to Permission-Based Access Control in JavaScript
When building web applications, access control is one of the key areas to focus on. It determines what actions users can take based on their role or status within the system. Recently, I was asked to look at a JavaScript function that handled access control based on user roles. However, it became clear that this approach would get out of hand very quickly, particularly as the number of roles and permissions grew.
The Problem with Role-Based Access Control
In traditional role-based access control (RBAC), each user is assigned a specific role, such as admin, editor, viewer, etc. Each role is then responsible for defining what the user can and cannot do. While this seems straightforward, it quickly becomes unwieldy when your application needs to handle more complex permission scenarios.
For example, an editor may be able to edit content but should not have access to administrative features, while a moderator might be able to manage comments but not edit the content itself. As the system grows, the logic tied to roles becomes more complex, and you end up with deeply nested conditions and redundant checks throughout your codebase.
This is where a permissions-based approach can simplify things.
Moving to Permission-Based Access Control
Instead of coupling functionality to roles directly, a better approach is to use permissions and then assign those permissions to roles. This way, each role is defined by a set of permissions it holds, and you can add, remove, or update permissions without touching every part of your code that checks for access.
Example Permissions
Here’s a simple example of how permissions can be defined:
type Permission = 'can_edit' | 'can_delete' | 'can_view' | 'can_view_reports' | 'can_manage_users';
// Map permissions to roles:
const rolePermissions: { [key: string]: Permission[] } = {
admin: ['can_edit', 'can_delete', 'can_view_reports', 'can_manage_users'],
editor: ['can_edit', 'can_view'],
viewer: ['can_view'],
guest: [],
moderator: ['can_edit', 'can_view'],
support: ['can_view_reports'],
};
In this case, we have five roles, and each role is associated with a set of permissions that define what the role can do.
Function to Get Permissions Based on Role
With a permissions-based structure, you can write a function that checks which permissions are available for a specific role:
const getUserPermissions = (role: string): Permission[] => {
const permissions = rolePermissions[role];
if (!permissions) {
console.warn(`Role "${role}" does not exist, defaulting to no permissions.`);
return [];
}
return permissions;
};
If the role doesn't exist, we can simply return an empty array and log a warning. This allows the application to fail gracefully if there’s a typo or unrecognized role.
Let’s take a look at how this approach works in practice. For example, if we want to check if a user with the editor role has permission to edit content:
const userRole = 'editor';
const userPermissions = getUserPermissions(userRole);
const actionEditUsers = () => {
if (userPermissions.includes('can_edit')) {
console.log(`%cPermission granted, ${userRole} can edit`, 'color: #8CC152');
} else {
console.log(`%cPermission denied, ${userRole} cannot edit`, 'color: #DA4453');
}
};
actionEditUsers();
Here, we first retrieve the permissions for the editor role, then check if the user has the can_edit permission. The console will display either a success or error message based on the permission check.
Similarly, we can check if the user has access to view reports:
const actionViewReports = () => {
if (userPermissions.includes('can_view_reports')) {
console.log(`%cPermission granted, ${userRole} can view reports`, 'color: #8CC152');
} else {
console.log(`%cPermission denied, ${userRole} cannot view reports`, 'color: #DA4453');
}
};
actionViewReports();
Benefits of Permission-Based Access Control
By transitioning from role-based access control to a permission-based system, we gain several advantages:
- Flexibility: New permissions can be added without refactoring multiple functions or adding complex nested conditions.
- Maintainability: Changes to roles or permissions are centralized, making it easier to manage and scale the application.
- Granularity: Permissions allow for more fine-grained control over what each role can do, making it easier to handle complex scenarios where a role might have limited access to certain features.
Access control is a critical aspect of web application development. By adopting a permission-based approach, we can simplify our logic, reduce complexity, and make the system more maintainable over time. This method allows us to grow our applications without worrying about role-specific logic sprawling across the codebase. It’s a scalable and flexible way to handle permissions that makes it easier to manage user capabilities, especially as the number of roles and features grows.

Written by
Steven Noble
Steven Noble is the founder of Graphics Cove, a senior full-stack engineer with 19 years building web products for startups and established companies. He writes about engineering, delivery and running a technical practice.