63

Role-Based Access Control (RBAC) and React Apps

 5 years ago
source link: https://www.tuicool.com/articles/hit/zA36fyR
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

TL;DR:In this article, you will learn how to implement Role-Based Access Control (RBAC) in React apps properly. First, you will take a brief look into what authorization and authentication is. Then, you will look at a naive strategy that is used constantly while securing React apps. In the end, you will learn what RBAC is, see the working example, learn what makes it a better strategy, and how you can implement it in React. All the code is available in this GitHub repository .

"Learn how to handle authorization in your React app with the Role-Based Access Control strategy."

TWEET THIS feuuMfj.png!web

Introduction to Role-Based Access Control

Web apps are everywhere, and many tasks can be performed on the front-end now. However, not all tasks should be available to every user since each user is unique and perform different roles. Some users are admins with special privileges, some are normal users, and some are simply visitors (unauthenticated). Web apps should be smart enough to distinguish between the different types of user roles.

Role-Based Access Control (RBAC) is a very popular method to achieve this. This article will be a guide on RBAC and also explain how to implement RBAC in a React app usingAuth0 and the new React Context .

What is Authorization and Authentication

Authorization is a process in which developers control the access to information by users and also limit the actions users can perform.

An authenticated user is a user who has identified themselves somehow (e.g., through a set of credentials). Authentication doesn't deal with access control. This is where authorization takes part in. Without proper authorization, a user can cause havoc in an app. A writer could delete other writer's articles or change their password. This is why authorization is as important as authentication.

Scope of Authorization in React Apps

Since many of the heavy liftings is now performed on the client side (on single-page applications), the scope of authorization has now increased more than ever.

A solid authorization system has to:

  • Limit the AJAX requests performed (even if the backend is properly secured, avoiding it will save you money).
  • Deny access to certain client-side routes.
  • Display or hide certain pieces of UI.

Suppose you were building the next Medium (a blogging app) . When users visit their profile page, you should guarantee that only them should see the Edit Profile button and that other would see only what articles they published so far.

Implementing Authorization in React Apps

To learn about authorization in React apps, in this article, you will build a simple blogging app. You won't waste time implementing features that are not important for this topic (like a backend), so you can focus on what matters: learning how to implement RBAC in React apps properly. However, before learning about the correct implementation, you will see what developers usually do so you can identify gaps and opportunities.

For starters, you blogging app will have the following requirements:

  1. A Home page and a Dashboard page.
  2. The Home page will be a public page that anyone can access and see all post published so far.
  3. The Dashboard page will show the logged in user details and a list of all posts written by them. The page can be visited by writers and admins only.
  4. Writers can edit their posts only, but admins can edit and delete any post.

To implement this, you will need to authenticate users and allot roles to them. Based on the role, your app will decide whether or not it should render the edit and delete buttons. On the same basis, you app will redirect users from dashboard route to home route if they are not logged in.

To facilitate the process, you will useAuth0 to quickly add authentication in your React app without any need for a backend.

Note:The content in this tutorial doesn't depend on Auth0. As such, if you already have an authentication system in place and just want to know more about Authorization and RBAC, you can skip the next section.

Scaffolding the React App

To see the big picture and to see RBAC in action, you are going to create a React app from scratch. Luckily, with the create-react-app tool, you can do it quite fast:

npx create-react-app rbac-tutorial-app

Note:The command above depends on [email protected] . If you don't have Node and NPM installed in your machine, please, check this page . If you have previous versions, you will have to install create-react-app globally first .

Now, move into the directory that create-react-app created, and run these commands:

# move into your new project
cd rbac-tutorial-app

# then move into the src directory
cd src

# remove files you won't need
rm App.js App.test.js App.css index.css logo.svg registerServiceWorker.js

# and create the ones you will use
mkdir components pages
touch pages/dashboard.js pages/home.js

The directory structure that you will end up with contains a components directory that will hold all the reusable components. Also, it contains a directory called pages with one file for each route (i.e., dashboard.js and home.js ).

Now, you will have install React Router to easily handle routing in your app. So, move up to the rbac-tutorial-app and install both react-router and react-router-dom :

# move to rbac-tutorial-app
cd ..

# and install both libraries
npm install react-router react-router-dom

Next, open the src/index.js file in your preferred IDE and replace its code with the following one:

import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';

import HomePage from './pages/home';
import DashboardPage from './pages/dashboard';

function App() {
  return (
    <div className="App container">
      <div className="jumbotron">
        <Router>
          <Switch>
            <Route exact path="/" component={HomePage}/>
            <Route path="/dashboard" component={DashboardPage}/>
          </Switch>
        </Router>
      </div>
    </div>
  );
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App/>, rootElement);

As you can see, you are using react-router-dom to define two routes. The first one for home ( path="/" ) and the other one for the dashboard ( path="/dashboard" ). To implement these pages, you can start by opening the src/pages/home.js file and pasting this:

import React from 'react';

const HomePage = () => (
  <h2>Home</h2>
);

export default HomePage;

Then you can open the src/pages/dashboard.js file and paste this code:

import React from 'react';

const DashboardPage = () => (
  <h2>Dashboard</h2>
);

export default DashboardPage;

With that covered, you can fire up the local dev server to see if everything is working:

# run the local server
npm start

This command will make your app run on http://localhost:3000 . On visiting this page, you will see the HomePage component rendered and, on visiting http://localhost:3000/dashboard , you will see the DashboardPage component.

Next, to make things a little bit more appealing, you will configure Bootstrap . To do so, open the index.html file (which resides in the public directory) and add this link element in the head section.

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- ... leave other tags like meta, link, and title untouched ... -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
  </head>
  <!-- ... leave body untouched ... -->
</html>

Now, you will focus on adding authentication with Auth0 into your React app.

Authentication in React Apps with Auth0

Now that you scaffolded your React application, you will integrate it with Auth0. If you don't have an Auth0 subscription yet, now it is a good time to sign up for a free one .

After signing up to your free Auth0 account, you will have to register your React app in the dashboard. To do so, go to theApplications page and click on Create Application . After clicking on it, Auth0 will show a dialog. In this dialog insert "RBAC and React" in the Name field and select Single Page Web Application as the type.

Then, when you click on the Create button, Auth0 will redirect you to the Quick Start section of your new application. From there, click on the Settings tab and insert http://localhost:3000/callback in the Allowed Callback URLs field. Auth0 requires this configuration as a security measure. That is, after authenticating, Auth0 will only redirect users to URLs registered in this field.

Now, hit the Save Changes button on the bottom of the page ( Ctrl/Cmd + S also works) and leave this page open. Soon, you will need to copy a couple of values from it.

Assigning Roles to Users with Auth0

To assign roles to your users with Auth0, you will useAuth0 Rules. As the documentation states:

"Rules are functions written in JavaScript that are executed when a user authenticates to your application. They run once the authentication process is complete, and you can use them to customize and extend Auth0's capabilities." -Auth0 Rules

This makes them perfect for what you need. So, to create this rule, go tothe Rules section in your Auth0 dashboard and click on Create Rule . On the page that Auth0 shows, choose the empty rule template. This will make Auth0 show a screen where you will be able to define a name for your rule and the code that it will execute when users authenticate.

eYrQ3ir.png!web

In this screen, set the rule name to something like "RBAC and React" and replace the code presented with the following one:

function (user, context, callback) {
  user.app_metadata = user.app_metadata || {};

  if (user.email === '[email protected]') {
    user.app_metadata.role = 'admin';
  } else {
    user.app_metadata.role = 'writer';
  }

  auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
    .then(() => {
      context.idToken['https://rbac-tutorial-app/role'] = user.app_metadata.role;
      callback(null, user, context);
    })
    .catch((err) => {
      callback(err);
    });
}

The code here is quite easy to understand. First, it is checking if the user that is authenticating has [email protected] as their email address (you will have to change this to your own email) and, if they do, it is setting admin as their role ( app_metadata.role ). If they don't, this code is configuring them as writers ( app_metadata.role = 'writer' ).

After that, the code updates the user profile ( updateAppMetadata ) with the new information and ends up setting the role as a custom claim to the https://rbac-tutorial-app/role namespace of the ID token that Auth0 returns. Having that in mind, you can click on the save button to make your rule active.

Note:If you don't understand what an ID token is, why it's needed, and what a custom claim is, don't worry. You don't need to understand the details right now. However, after reading this article, please, check the ID token docs at Auth0 .

Integrating Auth0 in your React App

To integrate Auth0 in your React app, the first thing you will need to do is to install the auth0-js library :

# from the rbac-tutorial-app
npm install auth0-js

Next, to configure this library with your Auth0 Application details, you will create a file called auth0-variables.js inside the src directory and paste the following code into it:

export const AUTH_CONFIG = {
  domain: "<YOUR_AUTH0_DOMAIN>",
  roleUrl: "https://rbac-tutorial-app/role",
  clientId: "<YOUR_AUTH0_CLIENT_ID>",
  callbackUrl: "http://localhost:3000/callback"
};

Note:You will have to replace <YOUR_AUTH0_DOMAIN> with the Domain property (e.g., blog-samples.auth0.com ) and <YOUR_AUTH0_CLIENT_ID> with the Client ID property that Auth0 shows in your Auth0 application .

Then, make your integration with Auth0 available to multiple React components, you will leverage the new React Context API . To do so, create a file called authContext.js inside the src directory and insert the following code into it:

import { createContext } from "react";

const authContext = createContext({
  authenticated: false, // to check if authenticated or not
  user: {}, // store all the user details
  accessToken: "", // accessToken of user for Auth0
  initiateLogin: () => {}, // to start the login process
  handleAuthentication: () => {}, // handle Auth0 login process
  logout: () => {} // logout the user
});

export const AuthProvider = authContext.Provider;
export const AuthConsumer = authContext.Consumer;

The code above uses the createContext method of React to create a constant called authContext that exposes six things:

  • A boolean called authenticated that holds the information if there is a user authenticated or not.
  • An object called user to store the details (profile) of the user that authenticates.
  • An accessToken that can be used to communicate with an API.
  • And three functions ( initiateLogin , handleAuthentication , and logout ) to handle the authentication process of your app.

With that in place, you will create an Auth component that will provide its children access to authContext . To do so, create a file named Auth.js inside src/components directory and insert the following code into it:

import React, {Component} from "react";
import auth0 from "auth0-js";

import {AUTH_CONFIG} from "../auth0-variables";
import {AuthProvider} from "../authContext";

const auth = new auth0.WebAuth({
  domain: AUTH_CONFIG.domain,
  clientID: AUTH_CONFIG.clientId,
  redirectUri: AUTH_CONFIG.callbackUrl,
  audience: `https://${AUTH_CONFIG.domain}/userinfo`,
  responseType: "token id_token"
});

class Auth extends Component {
  state = {
    authenticated: false,
    user: {
      role: "visitor"
    },
    accessToken: ""
  };

  initiateLogin = () => {
  };

  logout = () => {
  };

  handleAuthentication = () => {
  };

  setSession(authResult) {
  }

  render() {
    const authProviderValue = {
      ...this.state,
      initiateLogin: this.initiateLogin,
      handleAuthentication: this.handleAuthentication,
      logout: this.logout
    };
    return (
      <AuthProvider value={authProviderValue}>
        {this.props.children}
      </AuthProvider>
    );
  }
}

export default Auth;

This is a basic skeleton of the Auth component. In this component, you store the data inside the state and assign AuthProvider the state and methods you want to share with children components of Auth .

With that in mind, you can implement the methods.

initiateLogin = () => {
  auth.authorize();
};

Here, you call the authorize method of auth to redirect users to the Auth0 login page. When users fill the form and complete the authentication process, Auth0 redirects them back to your app at http://localhost:3000/callback . The redirect URL, in this case, will contain some important information for your app (like the ID token). To fetch these informations, you will make handleAuthentication call the parseHash method of auth :

handleAuthentication = () => {
  auth.parseHash((error, authResult) => {
    if (error) {
      console.log(error);
      console.log(`Error ${error.error} occured`);
      return;
    }

    this.setSession(authResult.idTokenPayload);
  });
};

Then you will make parseHash call setSession with the user profile ( idTokenPayload ):

setSession(data) {
  const user = {
    id: data.sub,
    email: data.email,
    role: data[AUTH_CONFIG.roleUrl]
  };
  this.setState({
    authenticated: true,
    accessToken: data.accessToken,
    user
  });
}

The setSession is a method that takes the data provided by handleAuthentication and saves it in the state of Auth component. The role received is the one computed by the rule you created in your Auth0 dashboard. In this case, it will be available via the data["https://rbac-tutorial-app/role"] property (note that it uses the same namespace used while creating the Auth0 Rule: https://rbac-tutorial-app/role ).

Finally, to enable users to log out, you just need to reset the state and revert their role to visitor :

logout = () => {
  this.setState({
    authenticated: false,
    user: {
      role: "visitor"
    },
    accessToken: ""
  });
};

The Auth component will now look like this .

With that covered, it is time to use the Auth component in your React app. So, in the src/index.js file, wrap the <Router> component inside Auth . Also, you will have to add a route for the http://localhost:3000/callback URL so that, when Auth0 redirects users back, you can run the handleAuthentication method:

import React from "react";
import ReactDOM from "react-dom";
import {BrowserRouter as Router, Route, Switch} from "react-router-dom";

import HomePage from "./pages/home";
import DashboardPage from "./pages/dashboard";
import CallbackPage from "./pages/callback";
import Auth from "./components/Auth";

function App() {
  return (
    <div className="App container">
      <Auth>
        <div className="jumbotron">
          <Router>
            <Switch>
              <Route exact path="/" component={HomePage}/>
              <Route path="/dashboard" component={DashboardPage}/>
              <Route path="/callback" component={CallbackPage}/>
            </Switch>
          </Router>
        </div>
      </Auth>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App/>, rootElement);

If you take a close look, you will realize that you haven't created the Callback component responsible for this route yet. To do so, create a file named callback.js inside src/pages directory. When users are redirected to this page, you will check if the URL contains important information and, if it does, you call the handleAuthentication method provided by the authContext . To implement this component, insert the following code into the callback.js file:

import React from "react";
import {Redirect} from "react-router-dom";

import {AuthConsumer} from "../authContext";

const Callback = props => (
  <AuthConsumer>
    {({handleAuthentication}) => {
      if (/access_token|id_token|error/.test(props.location.hash)) {
        handleAuthentication();
      }
      return <Redirect to="/"/>;
    }}
  </AuthConsumer>
);

export default Callback;

Now, you will quickly implement two components to help with the authentication process: the Login and Logout buttons. To do so, create a file called Login.js inside src/components directory with this code:

import React from "react";

import { AuthConsumer } from "../authContext";

const Login = () => (
  <AuthConsumer>
    {({ initiateLogin }) => (
      <button className="btn btn-sm btn-primary" onClick={initiateLogin}>
        Login
      </button>
    )}
  </AuthConsumer>
);

export default Login;

Then, create a file called Logout.js inside the same directory with this code:

import React from "react";

import { AuthConsumer } from "../authContext";

const Logout = () => (
  <AuthConsumer>
    {({ logout }) => (
      <button className="btn btn-sm btn-default" onClick={logout}>
        Logout
      </button>
    )}
  </AuthConsumer>
);

export default Logout;

After creating these components, you can update the Home component to enable users to log in:

import React from "react";
import { Redirect } from "react-router-dom";

import { AuthConsumer } from "../authContext";
import Login from "../components/Login";
import PostsList from "../components/PostsList";

const HomePage = () => (
  <AuthConsumer>
    {({ authenticated }) =>
      authenticated ? (
        <Redirect to="/dashboard" />
      ) : (
        <div>
          <h2>Welcome to React RBAC Tutorial.</h2>
          <Login />
          <PostsList />
        </div>
      )
    }
  </AuthConsumer>
);

export default HomePage;

Here, you are checking if there is an authenticated user. If they are, you redirect them to the Dashboard page. Otherwise, you show the Login button component and the PostList component (you will implement it later).

Handling Authorization in React Apps: the Naive Way

Without thinking too much (or without experience), developers end up using the user role with a bunch if statements to conditionally render certain parts of UI. This naive approach usually looks like this:

renderButton(user) {
  const markup = [];
  if(user.role === 'admin' || user.role === 'writer') {
    markup.push(
      <button>
        Action Button
      </button>
    )
  } else {
    markup.push(
      <span>Action Not Allowed</span>
    )
  }
  return markup;
}

This can get the job done, but there are many small issues hidden here that will show their ugly face when you scale (grow) your app.

Flaws in the Naive Strategy

The following list shows some flaws that you will encounter if you end up following this strategy:

  1. When requirements change, you will have to check each and every condition related to authorization and may have to update them. This is not at all scalable.
  2. For each new role added, the whole codebase has to be modified.
  3. The code is imperative whereas React preaches declarative code as it is easier to follow.
  4. There is more room for bugs to hide. As more conditions are added to accommodate the authorization logic, more conditional expressions will have to be placed. Having a combination of those can quickly become overwhelming. There might be unknown effects of the combination of these conditionals. It might happen that because you add a new role, the resulting logic now allows access to visitor also which wasn't desired. Proper tests can catch them, but there must be better ways.
  5. Code duplication occurs at a lot of places.

"A naive implementation of the Role-Based Access Control strategy can give you headaches."

TWEET THIS feuuMfj.png!web

Role-Based Access Control: a Better Solution

Role-Based Access Control authorization is a great way to solve these issues. In this section, you will learn more about this strategy and how to implement it in your React app properly.

What is Role-Based Access Control

Basically speaking, Role-Based Access Control is a scheme in which you define some roles, and then each user is assigned a specific role. Each one of these roles has some permissions associated with it, and all these permissions are defined at one place. To check if some information can be accessed, your code check if the role of the user has the necessary permissions.

Advantages of the Role-Based Access Control Strategy

Among the advantages of using Role-Based Access Control, it is important to highlight that:

  1. Since all the permissions are at one place, when requirements change, you just have to add or remove permissions of the roles.
  2. Creating new roles is very easy.
  3. The strategy favors declarative programming because all the roles and the permissions associated with them are explicitly defined.

Role-Based Access Control Example in React Apps

To start using Role-Based Access Control in your React app, you will make a file containing all the rules (defining all the permissions of these roles). These rules declare what permissions each role have.

Your blogging sample app will contain two types of permissions for each user role: a static and a dynamic. Static permissions are those permissions which don't need any data apart from the user role. Dynamic permissions are permissions which need additional data to determine access. Static permissions are helpful when, for example, you have to allow write access to writers and admins only. Dynamic permissions are helpful when, for example, you have only to allow the writer who is the owner of a resource to edit that resource.

So, for starters, create a file named rbac-rules.js inside the src folder and insert the following code into it:

const rules = {
  visitor: {
    static: ["posts:list", "home-page:visit"]
  },
  writer: {
    static: [
      "posts:list",
      "posts:create",
      "users:getSelf",
      "home-page:visit",
      "dashboard-page:visit"
    ],
    dynamic: {
      "posts:edit": ({userId, postOwnerId}) => {
        if (!userId || !postOwnerId) return false;
        return userId === postOwnerId;
      }
    }
  },
  admin: {
    static: [
      "posts:list",
      "posts:create",
      "posts:edit",
      "posts:delete",
      "users:get",
      "users:getSelf",
      "home-page:visit",
      "dashboard-page:visit"
    ]
  }
};

export default rules;

You can choose to define your permissions in any format (other than the resource:action approach used above), but it is very important to stay consistent.

Next, you will create a component to use these rules to determine access.

Implementing the Can Component

After creating the file that contains your RBAC rules, you will implement a component called Can . This component takes the rules that you defined and decides whether or not users can perform the desired action or see some part of the UI. If the answer is yes, then the Can component renders the yes prop. Otherwise, the component renders the no prop.

To define this component, create a new file called Can.js inside the components directory and insert the following code into it:

import rules from "../rbac-rules";

const check = (rules, role, action, data) => {
  const permissions = rules[role];
  if (!permissions) {
    // role is not present in the rules
    return false;
  }

  const staticPermissions = permissions.static;

  if (staticPermissions && staticPermissions.includes(action)) {
    // static rule not provided for action
    return true;
  }

  const dynamicPermissions = permissions.dynamic;

  if (dynamicPermissions) {
    const permissionCondition = dynamicPermissions[action];
    if (!permissionCondition) {
      // dynamic rule not provided for action
      return false;
    }

    return permissionCondition(data);
  }
  return false;
};

const Can = props =>
  check(rules, props.role, props.perform, props.data)
    ? props.yes()
    : props.no();

Can.defaultProps = {
  yes: () => null,
  no: () => null
};

export default Can;

In this component, you created a check() function that imperatively checks whether the user has the permission or not. If they do, the function returns true otherwise false . Then, the returned value is used by the Can component to render the appropriate prop declaratively. The first thing that the check() function does is to find if the permission is present in the staticPermissions array of the user role. If it is, then the user has the permission. If it is not present, then the function checks if the dynamicPermissions object has a property with the name of the permission. If there is, then, the function calls the method associated with that property and uses the returned value of the method to find if the user has the permission or not.

Using the Can Component

Using the Can component is very easy. You just have to pass the role and the perform prop to tell the component what is the user role and what action they are trying to execute. Then, you have to provide the yes and no props to the component.

With these informations, the component decides which props it will render. For example, in the code snippet below, if the logged in user can perform the dashboard-page:visit action, then the Can component will render the "User can do it" heading. Otherwise, the component will render the "User can't do it" heading:

<Can
  role={user.role}
  perform="dashboard-page:visit"
  yes={() => (
    <h2>User can do it</h2>
  )}
  no={() => <h2>User can't do it</h2>}
/>

Now that you know how to use the Can component, you can replace the code inside the dashboard.js file to use it:

import React from "react";
import { Redirect } from "react-router-dom";

import { AuthConsumer } from "../authContext";
import Can from "../components/Can";
import Logout from "../components/Logout";
import Profile from "../components/Profile";
import PostsList from "../components/PostsList";

const DashboardPage = () => (
  <AuthConsumer>
    {({ user }) => (
      <Can
        role={user.role}
        perform="dashboard-page:visit"
        yes={() => (
          <div>
            <h1>Dashboard</h1>
            <Logout />
            <Profile />
            <PostsList />
          </div>
        )}
        no={() => <Redirect to="/" />}
      />
    )}
  </AuthConsumer>
);

export default DashboardPage;

In the new version of this component, you check if the user can perform dashboard-page:visit and, they can, you render the dashboard page content (that is, you render things like the Logout button, profile data, list of Posts etc.). If they can't, you redirect the user to the home page.

With this in place, you will have to implement the two missing components: Profile and PostList . So, for starters, create a new file called Profile.js inside the components directory and insert the following code into it:

import React from "react";

import {AuthConsumer} from "../authContext";

const Profile = () => (
  <AuthConsumer>
    {({user}) => (
      <div>
        <h2>User Profile</h2>
        <ul>
          <li>ID: {user.id}</li>
          <li>Email: {user.email}</li>
          <li>Role: {user.role}</li>
        </ul>
      </div>
    )}
  </AuthConsumer>
);

export default Profile;

As you can see, the only thing this component does is to use the AuthConsumer define in the authContext file to output the profile of the user.

Next, you will create the PostsList component that is missing. This component will render a table containing a list of all the posts. The posts written by the user logged in will show an Edit button alongside them. Admins will also see the Edit button but, besides that, they will see Delete buttons.

To define this component, create a file called PostsList.js in the components directory and insert the following code into it:

import React from "react";

import posts from "../posts";
import {AuthConsumer} from "../authContext";
import Can from "./Can";

const PostsList = () => (
  <AuthConsumer>
    {({user}) => (
      <div>
        <h2>Posts List</h2>
        <table className="table">
          <thead>
          <tr>
            <th scope="col">#</th>
            <th scope="col">Title</th>
            <th scope="col">Actions</th>
          </tr>
          </thead>
          <tbody>
          {posts.map((post, index) => (
            <tr key={post.id}>
              <th scope="row">{index + 1}</th>
              <td>{post.title}</td>
              <td>
                <Can
                  role={user.role}
                  perform="posts:edit"
                  data={{
                    userId: user.id,
                    postOwnerId: post.ownerId
                  }}
                  yes={() => (
                    <button className="btn btn-sm btn-default">
                      Edit Post
                    </button>
                  )}
                />
                <Can
                  role={user.role}
                  perform="posts:delete"
                  yes={() => (
                    <button className="btn btn-sm btn-danger">
                      Delete Post
                    </button>
                  )}
                />
              </td>
            </tr>
          ))}
          </tbody>
        </table>
      </div>
    )}
  </AuthConsumer>
);

export default PostsList;

As you can see, this component imports an array of posts from the posts.js module (you will create this next). Then, it iterates through the array and renders table rows. In the table row, the component uses the Can component twice, one for Edit buttons and another for Delete buttons.

Since the no prop is not provided, it will default to null . This will make React render nothing in place of the buttons if the current user does not have to required permission. For the Edit button, the component pass additional data to the Can component. In this case, you need this because the owner of the post should also see the Edit button and that is a dynamic permission.

To wrap things up, you will have to define the posts.js file also. So, create this file inside the src directory with the following code:

const posts = [
  {
    id: "a",
    title: "Post a",
    ownerId: ""
  },
  {
    id: "b",
    title: "Post b",
    ownerId: ""
  },
  {
    id: "c",
    title: "Post c",
    ownerId: ""
  },
  {
    id: "d",
    title: "Post d",
    ownerId: ""
  }
];

export default posts;

Note that the code snippet above contains empty strings to all the fake posts. To see everything in action, you will have to log in with different users, take note of their ID through the http://localhost:3000/dashboard page , and replace the empty strings passed to the ownerId properties with these IDs.

To start your app, run the following command from the rbac-tutorial-app directory:

# from the rbac-tutorial-app dir
npm start

This will make React open your app in your browser:

A7Nr2eN.png!web

As you are not authenticated yet, the Can component is blocking React from rendering the Edit and Delete buttons. Now, if you click on the Login button and you log in with the email that you set for the admin user while creating the Auth0 Rule, you will see the following screen:

e2M3i2i.png!web

As you can see, now the Can component allows React to render both the Edit and Delete buttons.

At last, you have to check the case when a writer logs in. To do so, log in with another user account that does not use the same email address as the admin (you can sign up with a fake email address for this test). Then, you will have to take note of the ID (e.g., auth0|4b7695fff1642422bc85bf93 ) of the user logged in, and update one (or more) of the ownerId properties in the posts.js file.

After that, if you refresh the page and sign in again with the same email address, you will see a screen with the Edit button on the posts that you updated:

7BVb6zN.png!web

"I just implemented the Role-Based Access Control strategy in a React app with."

TWEET THIS feuuMfj.png!web

Conclusion

In this article, you had the chance to see Role-Based Access Control implemented in a React application properly. Also, to avoid making common mistakes, you took a quick glimpse of what a naive implementation would look like. Now, you have the knowledge and the tools necessary to develop your next React app with a proper authorization schema in place.

I hope this article helped you understand how to use RBAC in a React project practically. Please share it so others can benefit too. Follow me on Twitter and Medium to learn fun new things.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK