Search code examples
reactjsreact-router-domrole-based-access-control

React.js Simple Role Based Routes


It's the first time I am working with role based routes. We have a fully working app for players. Now, we need to add Trainer page for that app. We store the users in Firebase all we need to add new key for users something like trainer: true|false manually. And in React Application we will redirect the users. For example:

    if (user.trainer) {
      <Redirect to={"/trainer"}
    } else {
      <Redirect to={"/"}
    }

And we need to manage other pages. For example: when trainers want to navigate to another pages where it's not allowed we need to redirect them back to "/trainer" or the opposite normal player can't navigate to "/trainer" page. Can someone please help me with that ?

I have centralized routes like this;

import SignInSignUp from "./pages/SignInSignUp/SignInSignUp";
import Home from "./pages/Home/Home";
import Help from "./pages/Help/Help";
import Profile from "./pages/Profile/Profile";
import Exercises from "./pages/Exercises/Exercises";
import Trainer from "./pages/Trainer/Trainer";


export const routes = [
  { isProtected: false, path: "/auth", component: SignInSignUp },
  { isProtected: true, path: "/", component: Home },
  { isProtected: true, path: "/help", component: Help },
  { isProtected: true, path: "/profile", component: Profile },
  { isProtected: true, path: "/exercises", component: Exercises },
  { isProtected: true, path: "/trainer", component: Trainer },
];

And I loop the routes like that;

import "./App.scss";
import { Switch, Route, Redirect } from "react-router-dom";
import Navbar from "./components/Navbar/Navbar";
import ProtectedRouter from "./utils/ProtectedRouter";

import { routes, navbarPaths } from "./routes";

function App() {


  return (
    <div className="App">

      <Switch>
        {routes.map(
          ({
            isProtected,
            component,
            path,
          }: {
            isProtected: boolean;
            component: any;
            path: string;
          }) => {
            const RouteWrapper = isProtected ? ProtectedRouter : Route;
            return (
              <RouteWrapper
                exact
                key={path}
                path={path}
                component={component}
              />
            );
          }
        )}
        <Route exact path="/player*" render={() => (<Redirect to={"/"} />)} />          
      </Switch>

      <Route path={navbarPaths} exact component={Navbar} />
    </div>
  );
}

export default App;

And this is my Protected Route Component

import { Route, Redirect } from "react-router-dom";
import { useAuthStatus } from "../firebase/useAuthStatus.hook";

const ProtectedRouter = ({ component: Component, ...rest }: any) => {
  const { loggedIn, checkingStatus } = useAuthStatus();
  return (
    <Route
      {...rest}
      render={(props) => {
        if (!checkingStatus) {
          if (loggedIn) {
            return <Component />;
          } else {
            return (
              <Redirect
                to={{
                  pathname: "/auth",
                  state: {
                    from: props.location,
                  },
                }}
              />
            );
          }
        }
      }}
    />
  );
};

export default ProtectedRouter;

Solution

  • If I'm understanding your question correctly, you want to conditionally change the redirect target in your ProtectedRouter component based on a user's role.

    Not tested at all, but I believe the following will resolve your question.

    Refactor your ProtectedRoute to take the isProtected, trainer, and passed route props. If the route is unprotected, check the user's trainer role for a match and conditionally render a route or redirect to "/trainer" or "/". If the route is protected and the user is authenticated, again check the user's trainer role for a match and conditionally render a route or redirect to "/trainer" or "/".

    const ProtectedRouter = ({ isProtected, trainer, ...props }: any) => {
      const location = useLocation();
      const { loggedIn, checkingStatus } = useAuthStatus();
      const user = /* business logic to get user object */
    
      if (checkingStatus) return null;
    
      if (!isProtected || loggedIn) {
        return user.trainer === trainer
          ? <Route {...props} />
          : <Redirect to={user.trainer ? "/trainer" : "/"} />;
      }
    
      return (
        <Redirect
          to={{
            pathname: "/auth",
            state: { from: location },
          }}
        />
      );
    };
    

    Update the routes to add a trainer property.

    export const routes = [
      { isProtected: false, path: "/auth", component: SignInSignUp },
      { isProtected: true, path: "/", component: Home },
      { isProtected: true, path: "/help", component: Help },
      { isProtected: true, path: "/profile", component: Profile },
      { isProtected: true, path: "/exercises", component: Exercises },
      { isProtected: true, path: "/trainer", component: Trainer, trainer: true },
    ];
    

    Update App to map all the routes to the custom route component.

    function App() {
      ...
    
      return (
        <div className="App">
          <Switch>
            {routes.map(props => <ProtectedRouter key={props.path} exact {...props} />)}
            <Route exact path="/player*" render={() => (<Redirect to={"/"} />)} />          
          </Switch>
    
          <Route path={navbarPaths} exact component={Navbar} />
        </div>
      );
    }