DEV Community

loading...
Cover image for How to Role Based Access Control (RBAC) ? ๐Ÿ‘ฎโ€โ™‚๏ธโš ๏ธ

How to Role Based Access Control (RBAC) ? ๐Ÿ‘ฎโ€โ™‚๏ธโš ๏ธ

Arvind Narayan
I architect tech solutions for a living๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป. Tech Lead by day / procrastinating UX Designer otherwise
ใƒปUpdated on ใƒป4 min read

NOTE: For the sake of simplicity this article is going to be based on React JS along with React Router and Node JS on Express JS for backend, all of which are awesome and do check them out.

Have you ever wondered how in the world certain large scale applications like airbnb, facebook, youtube etc., manages to block a certain part of their application for certain people or release features based on certain conditions ? They all use a tried and tested methodology called RBAC this solution gained traction during the 90โ€™s and still is widely used in a lot of forms. RBAC stands for Role Based Access Control as the title says and this article helps you fathom the RBAC game.

...

Parameters to considers for RBAC :-

1. Route Guarding ๐Ÿ›ฃ๏ธ:
This is a process of authenticating ๐Ÿ”’ and authorising routes or part of a url in your web app. Typically you have a router switch with route paths couple. Here you can see that the routes stay in a different file to act as s single source of truth. In the switch instead of routes we wrap it with a wrapper and pass roles required path and component to be rendered as props.

module.exports = {
    homePage: {
        route: '/',
        roles: ['ROLE_TEACHER', 'ROLE_ADMIN', 'ROLE_EXTERNAL_SOURCER']
    },
    createQuestion: {
        route: '/question-bank/create/:type/',
        roles: ['ROLE_TEACHER', 'ROLE_ADMIN', 'ROLE_EXTERNAL_SOURCER']
    },
    testAttempt: {
        route: '/student/test/:gid',
        roles: ['ROLE_TEACHER', 'ROLE_ADMIN', 'ROLE_STUDENT']
    }
};

Role.js

<Switch>
  <RouteWrapper
    exact
    path={routePaths.homePage.route}
    roles={routePaths.homePage.roles}
    Component={QuestionListPage}
  />
  <RouteWrapper
    exact
    path={routePaths.createQuestion.route}
    roles={routePaths.createQuestion.roles}
    Component={CreateQuestions}
  />
  <RouteWrapper
    exact
    path={routePaths.testListPage.route}
    roles={routePaths.testListPage.roles}
    Component={TestListPage}
  />
  <Route path="/error/:code">
    <ErrorComponent />
  </Route>
  <Route path="">
    <Redirect to="/error/404" />
  </Route>
</Switch>

Router.js

const RouteWrapper = ({Component, ...props }) => {
    const auth = checkAuth();

    let role = getUserRole(),
        access = checkAccess(role, props.roles);

    if (!auth) {
        logout();
    } else if (auth && !access) {
        return <Redirect to="/error/401" />;
    }

    return (
        <Route
            {...props}
            render={routeProps => <Component {...routeProps} />}
        />
    );
};

Route.js

route wrapper here will check for authentication data and access based on the role needed by route and user role matrix. If you are un-authenticated the app will log you out to prevent conflicts, If you are authenticated but not authorised to access the route we will redirect to an error page. If your authenticated and authorised we will finally render the component. This check happens on all route changes for our protected routes.

2. Restricting access to a part of the page ๐Ÿš:
There might be certain situations where you might want to block features or a section of a page (route) while granting access to others. Hereโ€™s how we do it.

import { useSelector } from 'react-redux';
import { allowedUserChecker } from 'utils';
import PropTypes from 'prop-types';

const RBAC = ({ allowedRoles, children }) => {
    let userRoles = useSelector(state => state.userInfo.roles);
    let access = allowedUserChecker(userRoles, allowedRoles);
    return access && children;
};

RBAC.propTypes = {
    allowedRoles: PropTypes.arrayOf(PropTypes.string),
    children: PropTypes.element
};

export default RBAC;

Rbac.js

This is a RBAC HOC ( higher order components ) we get the user roles from a global store (can be through other means as well) and we try to derive the access based on user role matrix check. We render the wrapped component only if access is permitted.

import RBAC from './dir/RBAC';
...
<RBAC allowedRoles={[ 'ROLE_AUTHOR', 'ROLE_ADMIN']}>
    <Button type="submit" label="VERIFY AND PUBLISH" />
</RBAC>
...

3. Securing Backend Endpoints ๐Ÿ”—:
Last but not the least we need to make sure the backend secures their APIโ€™s from their end as well.

Well there are a ton of ways to do this using some API gateway, Proxy Servers etc., For the sake of simplicity Iโ€™m assuming certain parameters. These parameters are not necessary and you can implement the logic in way that you need as the crux is core logic only.

First thing first youโ€™ll need a role to routes file similar to what we saw in frontend.

module.exports = {
    homePage: {
        route: '/',
        roles: ['ROLE_TEACHER', 'ROLE_ADMIN', 'ROLE_EXTERNAL_SOURCER']
    },
    createQuestion: {
        route: '/question-bank/create/:type/',
        roles: ['ROLE_TEACHER', 'ROLE_ADMIN', 'ROLE_EXTERNAL_SOURCER']
    },
    testAttempt: {
        route: '/student/test/:gid',
        roles: ['ROLE_TEACHER', 'ROLE_ADMIN', 'ROLE_STUDENT']
    }
};

role.js

โ„น๏ธ This might be a duplicate but try to host this somewhere common to solve your DRY OCD ๐Ÿ’  issues if any ๐Ÿ˜‚ .

then this RBAC middleware below will help manage things for you automatically. Here itโ€™s pretty simple check access and authorise / respond with error.

module.exports = (rolesWithAccess) => {
  let {roles} = req.user;
  // eg:  roles = ['author'] comming from JWT token
  return (req, res, next) => {
    if (rolesWithAccess.length && !rolesWithAccess.includes(roles)) {
      return res.status(401).json({ message: 'Unauthorized' });
      // send however you want this one way of sending it.
    } else {
      next();
    }
  }
};

rbac middleware

Now we need to wire it up with our route like this. Thats it we got them unauthorised users ๐Ÿ›‘

const routes from './dir/routes';
const rbac from '/dir/to/rbac';
const publishController from '/dir/to/controllers';

...

app.use(routes.publish.route, rbac(routes.publish.roles), publishController);

...

La End

Stay tuned ๐Ÿ• for part 2 of this article I might write about implementation other platform such as Next JS and GraphQL.

Iโ€™m sure there are modifications / implementations to better the above article and also fun fact route paths can be a regex(/^\/(api|rest)\/.+$/) this is something that now many of you may know. Likewise, my work at upGrad is now only about learning new things to better my career but also to build a platform that empowers multiple people in transitioning their careers as well. Do visit upgrad.comto check out our programs that are completely online! If you wish to work with our ever-enthusiastic team, check out our careers page . We are always on the lookout for the ambitious, talented folks!

Discussion (2)

Collapse
krishnasharmacc profile image
Krishna Sharma • Edited

Hi @thearvindnarayan , great article indeed! One thing that i think might have been a mistake is in Router.js line 4: we need to pass role instead of userRole to the checkAccess() method. Other than that, i found this article to be really simple and well explained. Keep up the amazing work! Kudos ๐Ÿ‘๐Ÿ‘

Collapse
thearvindnarayan profile image
Arvind Narayan Author

Thanks Krishna for pointing it out. It flew under the radar! and thanks for the appreciation

Forem Open with the Forem app