Technology Blog

Look deep into latest news and innovations happening in the Tech industry with our highly informational blog.

Role based authorization in React

hkis

 
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?”

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.

role-based-authorization

That would work, but we 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.

// Route handler
class YourRoute extends React.Component {
  constructor(props) {
    super(props)
    // Load user from wherever into state.
  }
  render() {
    return <Authorization allowed={this.props.allowed} user={this.state.user}>
      {() => 
        /* the rest of your page */
      }
    <Authorization>
  }
}

export default YourRoute

// Router configuration
<Router history={BrowserHistory}>
  <Route path="/" component={App}>
    <Route
      allowed={['manager', 'admin']}
      path="feature"
      component={YourRoute}
    />
  </Route>
</Router>

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.

// Route handler
class YourRoute extends React.Component {
  render() {
    return <div>
      /* the rest of your page */
    </div>
  }
}

- export default Authorization(YourRoute, ['manager', 'admin'])
+ export default YourRoute

// Router configuration
<Router history={BrowserHistory}>
  <Route path="/" component={App}>
    <Route
      path="feature"
-     component={YourRoute}
+     component={Authorization(YourRoute, ['manager', 'admin'])}
    />
  </Route>
</Router>

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 withincomponent={} 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.

// Authorization HOC
- const Authorization = (WrappedComponent, allowedRoles) =>
+ const Authorization = (allowedRoles) =>
+  (WrappedComponent) =>
     return class WithAuthorization extends React.Component {
       constructor(props) {
         super(props)
// ...


// Router configuration
<Router history={BrowserHistory}>
  <Route path="/" component={App}>
    <Route
      path="feature"
-     component={Authorization(YourRoute, ['manager', 'admin'])}
+     component={Authorization(['manager', 'admin'])(YourRoute)}
    />
  </Route>
</Router>

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

// Router configuration
+ const Manager = Authorization(['manager', 'admin'])

<Router history={BrowserHistory}>
  <Route path="/" component={App}>
    <Route
      path="feature"
-     component={Authorization(['manager', 'admin'])(YourRoute)}
+     component={Manager(YourRoute)}
    />
  </Route>
</Router>

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

// Router configuration
const User = Authorization(['user', 'manager', 'admin'])
const Manager = Authorization(['manager', 'admin'])
const Admin = Authorization(['admin'])

<Router history={BrowserHistory}>
  <Route path="/" component={App}>
    <Route path="users" component={User(Users)}>
      <Route path=":id/edit" component={Manager(EditUser)} />
      <Route path="create" component={Admin(CreateUser)} />
    </Route>
  </Route>
</Router>

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 we thought would solve problems people are facing in the real world.

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.

For more Information and to build a website using React JS, Hire React Developer from us as we give you a high-quality product by utilizing all the latest tools and advanced technology. E-mail us any clock at – hello@hkinfosoft.com or Skype us: “hkinfosoft“.To develop your custom website using React JS, please visit our technology page.

Content Source:

  1. medium.com