In this post, let’s cover how we can manage granular roles and permissions in React.
Any React application starts off nice and clean until you start layering conditional logic on top of it. And it quickly gets worse when you start adding granular permissions and roles.
Handling all those nuances is a nightmare when using simple conditionals or
switch statements. There is a better way to do it.
Overview of the pattern
If you break it down, the pattern is simple. It involves building a gating wrapper and defining a set of maps with roles, scopes, and permissions.
The gating wrapper accepts an array of scopes the user must have to view protected components. We will also provide a couple of handy props for certain edge cases.
We will start by adding the gating component. Let’s call it
scopes properties. We use
scopes to declare which permissions the user must have to view the content of
useGetRole is a custom hook you must define that returns the current user’s role.
hasPermission returns a boolean of whether the user is granted access based on the scopes required and the user’s permissions.
Maps for roles and permissions
Now let’s define the scopes, user roles, and permissions maps we need. We’ll put them in separate
We have three maps:
ROLES- All the user roles in our application.
SCOPES- Scopes we pass to our gating wrapper.
PERMISSIONS- The map that defines the set of scopes each user role possesses.
Now let’s see
PermissionsGate in action. Let’s say the user must be an editor to view the page below:
All we need to do is wrap protected content with
PermissionsGate and provide scopes necessary to view the content.
The client API is clean, and it’s easy to tweak the scopes in the future if (when) business requirements change.
Additional use cases
So far, so good. But we can improve
PermissionsGate further. Let’s adapt it to cover two more uses cases that often come up:
- Display custom error message if the user doesn’t have correct permissions.
- Pass a custom set of props down the component tree if the user doesn’t have correct permissions.
Let’s start with the first one. In some cases, we want to display a custom error component if the user doesn’t have the right permissions. We can do so by adding a prop to our
PermissionsGate. Let’s call it
Let’s see it in action:
And here’s what we see now:
Instead of rendering protected components,
PermissionsGate renders our custom error message. That’s because we require
canCreate scope to view the content and our user doesn’t have it.
Let’s move on to the second use case. Sometimes, you don’t necessarily want to hide children. Instead, you might want to make children aware by passing a custom set of props. One example could be disabling an input if a user is not an editor by providing a
To pass custom props to children, we can use React.cloneElement :
And here’s how you use it:
Our input will now be disabled for anybody without the
PermissionsGate is ready! By handling these two edge cases, we made sure we can take on any permission-related requirement that gets thrown at us.
With our gating component, we have a clean and scalable solution that lets us get as granular with our permissions as we need to.
Depending on your server and database setup, your implementation of gating component and permission maps may vary slightly. But the main principle stays the same.
As a quick reminder: never rely on checking permissions on the front-end application alone. Always remember to add additional permission validation on your server as well.
If you’d like to get more web development, React and TypeScript tips consider
following me on Twitter,
where I share things as I learn them.