Good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy. The ultimate goal is to minimize the lifetime cost of the system and to maximize programmer productivity. - Robert C. Martin
React is evolving, yet still lacks guidelines when it comes to architecting the applications. But when we have to design enterprise-level applications, the most important thing that should be covered is clean architecture so that it's easy for developers to understand and collaborate.
Let's take an example of an application, where every user will have multiple roles and permissions to access the application. Of course, such applications can have condition-based logic for different roles and permissions.
Any React application can start well until we start adding condition-based logic on top of it. Soon it can start getting worse upon adding granular checks for roles and permissions for user access.
Holding all this logic and keeping track of roles and permissions is a cumbersome task when used with normal
conditional checks or
How did we architect this flow?
In one of our applications, we've dealt with roles and permissions-based user flow. In addition, we've token-based authorization in the same application so we were storing the token in
localStorage just to keep track of the authorized user. And all the user details like
context (user context).
To verify whether the logged-in user has specific role/permission to access specific
route/action item, we have implemented several hooks like
useHasRoles which were returning a boolean value if the current user has those specific
To check those specific permissions/roles, the hooks
useHasRoles need the current user context. So here we have two approaches, either we can directly access the
user context into these hooks or we can create a separate hook as
useCurrentUser which will act as a consumer for
In our application, we've used the 2nd approach that is implemented separate hook as
useCurrentUser instead of accessing context at other places. The
useCurrentUser hook will return the logged-in user. So from now onwards, throughout the lifetime of our application, whenever we have to access the logged-in user, instead of accessing context directly, we can simply call
useCurrentUser hook and get the current user details.
Refer to the 👆🏼 above example
Here we are having 4 different types of Roles
Also, there are a few sets of permissions that have been assigned to specific roles, based on permissions we've action buttons enabled on the landing screen post-login flow.
You can even change permissions in
getPermissions util located in
utils/roles.js file and observed the behavior for different role users.
The main task of the
useHasPermissions hook is to verify and return the boolean flag if the currently logged-in user has the permission which the app needs. We can either pass permission as
string or an
array of permissions.
This hook gets the current user context with the help of the
useCurrentUser hook. And checks for the
permission(s) needed with the permissions from
How to use
useHasPermissions hook ?
const canComment = useHasPermissions(['CAN_READ', 'CAN_ADD_COMMENT']); // checks for multiple permissions const canRead = useHasPermissions('CAN_READ'); // checks for single permission
The working of
useHasRoles is similar to
useHasPermissions, it will just verify the
Roles instead of
const hasAdminRole = useHasRoles(['ADMIN']); // checks for multiple roles const hasSuperAdminRole = useHasRoles('SUPER_ADMIN'); // checks for single role
useHasRoles as AuthGuard
In the above example, instead of verifying the
role to enable/disable the action button, we are checking whether a user has access to a specific route or not. Here, we've implemented
ProtectedRoute as higher-order component (HOC) which will act as AuthGuard for our application. It will only allow the authorized users to access the specific route.
Along with acting as
Auth Guard, it helps in claiming a specific route. There might be few routes in the application that should be accessed by users having specific roles. To claim route for specific roles, we just need to pass prop as
claimWithRole as a
role or an array of
As in the above example, we have the
/admin route which can be accessed by the user having roles
So if you try to log in using customer and click on the
Go to Admin link, then it will show the message as
You do not have specific roles/permissions to access this route.
Note: As here we've added claiming routes by Roles only, you can even claim a route by permissions. Just need to add few lines of code to check for desired permissions.
Hope this article was able to throw light on setting up roles and permissions-based architecture in React applications.