Carl Vitullo
Jan 20, 2017 · 4 min read
Photo by JESHOOTS.COM on Unsplash

Authentication and authorization are both common problems when writing an application. Authentication answers the question, “who are you?”, while authorization answers the question, “are you allowed to see that?”

Somebody recently asked how to accomplish role-based authorization using React and React Router, and linked to a post describing one way to go about it. Essentially, the post suggests simply passing a list of roles that are allowed to see a given route, and checking whether the currently logged in user is one of those roles within your route handler.

That would work, but I see a few problems with it.

  • Single responsibility principle violations.
  • Unnecessarily passing data through React Router.
  • No reusable code.

So how can we apply authorization in a way that gives each component a single job to do, is self contained, and is reusable?

One way we could improve upon the above blog’s code, would be to encapsulate the authorization logic in its own component—with a function child to prevent components from mounting if the user isn’t authorized—and render that from your route handler.

This gives us reusable authorization logic, which is a step in the right direction. We could even have the authorization component load the user itself, so all it takes as props are the allowed roles. But each route would still “own” the fact that authorization is taking place, which doesn’t feel right.

But there’s another option. A higher-order component — often abbreviated as HOC — could move the authorization logic completely outside of the route handler. Assuming the authorization HOC loads the currently logged-in user on its own, it could look like the following:

I almost didn’t include an implementation of the authorization HOC, because it depends on what you’re using to store your data, whether you want to show an error message or redirect somewhere else on failure, which router you’re using, etc. There are a lot of unknowns, and I want to focus on the pattern rather than the implementation details. The important thing is that it inspects the currently logged in user and renders the wrapped component as they have a sufficient role.

Because it doesn’t matter when the HOC is applied, we could move that function call into the router configuration.

Update: It’s been pointed out that wrapping a component in an HOC within render can cause a lot of problems, and as a rule, shouldn’t be done. In every case, replacing the inline HOC function call within component={} should be replaced with assigning it to a variable outside the render method.

const RouteWithAuthorization = Authorization(YourRoute, ['roles'])

For further reading on why this is a bad idea, check the React documentation. Back to the original post.

Great! Now there’s a hint of authorization logic in our route handler, nothing is being passed through React Router, and all of our allowed roles are defined in a single file. But you know what, if we have a lot of routes, that’s going to mean a lot of duplicated role definitions.

Let’s make two changes — flip the order of the arguments we give to the HOC, and “curry” the function. Our HOC becomes a function that returns an HOC. This type of function call should look familiar if you use Redux, as it’s the same mechanism connect uses.

Now, we can “seed” our authorization HOC with different levels of allowed roles, eliminating the need to define allowed roles over and over.

There, that’s better. Now we can define a set number of roles in advance, and we could have a router configuration like this:

Users are able to see everyone, managers can edit users, and admins can add new users. It’s clearly apparent what the intent is by looking at the usage, and each part of the code has a single responsibility.

Edit: To reiterate above, creating higher order components within render will cause problems with how React calculates diffs. In the final example above, the User(User), Manager(EditUser), and Admin(CreateUser) portions should be extracted from the render method and assigned to variables.

Of course, client-side authorization is only one part of it. The back end should always enforce user roles as well, because all data on the client can be changed from the devtools.

This is not a best practice, this is an idea that I thought would solve problems people are facing in the real world. I published this as a blog post instead of a library, because everyone’s use case is different.

Maybe in your app, the authorization HOC passes the current role as a prop into the component it’s wrapping. Maybe one of the curried arguments is a component to render if authorization fails. Maybe instead of a higher order component, it’s a component with a function child that gets the role and whether authorization succeeded as arguments. Maybe instead of an array of allowed roles, you pass a function with the signature user => bool.

As a P.S., React has a number of idioms that let you write really expressive, concise code once you’re familiar with them. Higher-order components, function children, and (basic) currying are concepts with very simple code: each of them takes only a few lines, but once you figure out what type of problems they solve, you can combine them in ways that make them extremely powerful.


Thanks for reading! I’m on Twitter as @vcarl_ (but most other places I’m vcarl). I moderate Reactiflux, a chatroom for React developers and Nodeiflux, a chatroom for Node.JS developers. If you have any questions or suggestions, reach out!

Better Programming

Advice for programmers.

Carl Vitullo

Written by

Just some guy. On Twitter https://twitter.com/cvitullo Also the internet https://blog.vcarl.com/

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade