User authorization with React/Redux
Here’s the problem. You want to prevent access to parts of your app for users that are not logged in, and additionally you want to prevent logged in users without the right role or access level (non-admins typically) from accessing yet more parts of your app.
There will probably be many slightly different ways to solve this problem, but here’s one I’ve had success with.
Which stack are you using in this tutorial?
You are using
redux and maybe even
react-router (and its trusty companion
Overview of solution
The idea is to create a Component that will render its children if the current user has the correct role, and then write a helper function that wraps another Component (typically a page) for more convenient usage with
The HasRole Component
We will start out with a Component that wont render its children unless the currently logged in User has the correct role. I’ve chosen to call this piece of work the HasRole component and it will be connected to the Redux store where the user role is expected to be available somewhere.
As a quick aside, for my most recent project I’m storing some info about the current user in a property called
ui which contains the state of the user interface. This property gets fetched every time the user reloads the page so that the basic view state always remains the same, and I might write an article about why I think this is a good idea at a later time.
Now back to the HasRole component, which I’ve put in a file called
HasRole.js and made to look like this:
I’ve added some helpers at the bottom that hardcodes the roles that I have in my application. This helps to avoid mistakes with filling in the correct properties and allows me to keep my code short and expressive.
HasRole (and companions
IsUser) can now be used like this:
This gets us most of the way and is perfect for quickly hiding/revealing stuff in your user interface. However, it does not solve the problem protecting entire pages, which we will now have a look at.
A helper for the router
Sometimes you will be looking for a way to restrict access single pages or subsets of pages based on how they are routed. We are now going to explore a way to make defining these restricted routes easy, preferably as part of the actual definition of the route instead of within the render function of the page.
react-dom along with
react-router-redux, you might have a
ReactDOM.render function that ends up looking something like this:
Now suppose you wanted to add a protected route only for logged in users at
/protected here, with an additional page only for admins at
/protected/admins. How can we use the concepts from our
HasRole component from earlier to accomplish this?
The easy solution would be to just wrap the render function for these pages in
IsUser and be done with it. This will work, but would show a completely blank page to the non-authorized user trying to open it which is less than ideal.
A better solution would be to create a wrapping function that receives the page component and returns a wrapped component for use in the router, that itself does one of the following three things:
- Redirects non-logged in users to the login page
- Shows a “Forbidden” page (
pages.Forbidden) for authenticated users that are not authorized to view the admin page.
- Allows the page to be rendered for users with the correct role (admins).
Here is my idea of a component like that, and I’ve chosen to call it
You might notice that the base component
RequireRoleBase looks and functions similar to
HasRole from earlier with some important distinctions. Firstly, instead of not rendering when the user does not have the correct role, it uses
react-router to redirect to either
/login when the user is not logged in, or
/forbidden when the logged in user does not have the correct role.
Secondly, if the prop
requiredRole has a falsy value, such as an empty string
'', it will render the child components. The reason for this is to enable the component to be used just as a check if the user is logged in our not, regardless of which role the user has.
Thirdly, a new variable
isRehydrated has been introduced that will be explained shortly. This boolean variable acts as a guard to prevent the user from being redirected before the redux state has settled.
It also takes extra care not to render any children when the above conditions have not been met, since doing so may trigger additional errors in the application (such as requests failing on the server side due to insufficient privileges) which we would like to avoid.
However, this is still not enough to use as a drop in in the router definition. To accomplish that, we need to export a function that wraps around the component of the page you are actually trying to protect. Adding to the end of
We first connect to the store, expecting the role of the current user to be available, but also two boolean values
isRehydrated. The first one has an obvious meaning, the second one relates to redux-persist which is the library I’m using to save the a part of the state of the redux store to
localStorage in the browser.
How to setup
redux-persist will be explained in a future post, but
isRehydrated is in any case set to
true when the saved state in
localStorage has been loaded back into the application state. This is important because the state is empty on page reload, and rendering this component just after that event will cause a redirect to
/login even if the user is actually logged in. Thus we need to wait for the state to be loaded back before we take any action with the router.
Now we are ready to use this component in our routing definition file, resulting in some quite clean code in my own opinion.
Don’t forget to setup a route for the 403 Forbidden page also!
Using the above code, any attempt to open
/protected will redirect the unauthenticated user to the
/login page. Users that are authenticated can open
/protected, but they must be authenticated as admins if they wish to open
/protected/admin, or else they will be redirected to
/forbidden - just like we wanted.
That is all for today. I hope you enjoyed this article, and some follow ups explaining some of the concepts above will be explored on in later articles!