React router dom on Permission - Implementing Access Control in React Apps



In a React application, for example, you could use the react-router-dom library to define routes for your application and a custom withAccessControl higher-order component to wrap your route components. The withAccessControl component would check the user's identity and permissions before rendering the wrapped component.


Let's take a look at my example, first is the typing types.ts :

export interface Route {
path: string;
component: React.ComponentType<any>;
title: string;
exact?: boolean;
acl: {
[key in Role]: {
type: DecisionType;
meta?: string;
};
};
}



export enum Role {
GUEST = 'guest',
USER = 'user',
ADMIN = 'admin',
}



export enum DecisionType {
ALLOW = 'allow',
DENY = 'deny',
REDIRECT = 'redirect',
}

Then routes.ts :

import { Route, Role, DecisionType } from './types';
import HomePage from './pages/HomePage';
import ProfilePage from './pages/ProfilePage';
import DashboardPage from './pages/DashboardPage';
import AdminPage from './pages/AdminPage';
import LoginPage from './pages/LoginPage';

export const routes: Route[] = [
{
path: '/',
component: HomePage,
title: 'Home Page',
exact: true,
acl: {
[Role.GUEST]: {
type: DecisionType.ALLOW,
},

[Role.USER]: {
type: DecisionType.ALLOW,
},

[Role.ADMIN]: {
type: DecisionType.ALLOW,
},

},

},

{

path: '/profile',
component: ProfilePage,
title: 'Profile Page',
exact: true,
acl: {

[Role.GUEST]: {
type: DecisionType.REDIRECT,
meta: '/login',

},

[Role.USER]: {
type: DecisionType.ALLOW,
},

[Role.ADMIN]: {
type: DecisionType.ALLOW,
},

},

},

{

path: '/dashboard',
component: DashboardPage,
title: 'Dashboard Page',
exact: true,

acl: {

[Role.GUEST]: {
type: DecisionType.REDIRECT,
meta: '/login',
},

[Role.USER]: {
type: DecisionType.ALLOW,
},

[Role.ADMIN]: {
type: DecisionType.ALLOW,
},

},

},

{

path: '/admin',
component: AdminPage,
title: 'Admin Page',
exact: true,
acl: {
[Role.GUEST]: {
type: DecisionType.REDIRECT,
meta: '/login',
},

[Role.USER]: {
type: DecisionType.REDIRECT,
meta: '/',
},

[Role.ADMIN]: {
type: DecisionType.ALLOW,
},
},
},

{
path: '/login',
component: LoginPage,
title: 'Login Page',
exact: true,
acl: {
[Role.GUEST]: {
type: DecisionType.ALLOW,
},

[Role.USER]: {
type: DecisionType.REDIRECT,
meta: '/',
},

[Role.ADMIN]: {
type: DecisionType.REDIRECT,
meta: '/',
},
},
},
];

This code exports an array of Route objects that represent the routes of a web application.
Each Route object contains the following properties:

path: a string representing the path of the route.
component: the component to be rendered when the route is accessed.

title: a string representing the title of the page associated with the route.

exact: a boolean value indicating if the route should be matched exactly or not.

acl: an object representing the access control list for the route. The acl object contains three properties, guest, user, and admin, which correspond to the three possible roles that a user can have. The value of each property is an object that specifies the access control decision for that role, including the type of decision (either ALLOW or REDIRECT) and the meta data associated with the decision (either the path to redirect to or null for ALLOW decisions).

This code also imports the Route, Role, and DecisionType interfaces from a types.ts module, which defines these interfaces for use throughout the application. Additionally, it imports five page components (HomePage, ProfilePage, DashboardPage, AdminPage, and LoginPage) that are associated with the routes.


Lastly is the App.tsx file that is the root of the application:

const withAccessControl = (Component: React.ComponentType, acl: RouteConfig['acl']) =>
(currentUserRole: Role) => {
const decision = acl[currentUserRole];
if (!decision || decision.type === DecisionType.ALLOW) {
return <Component />;
} else if (decision.type === DecisionType.REDIRECT) {
return <Redirect to={decision.meta || '/'} />;
}

return null;

};

const App = () => {
const currentUserRole = Role.USER; // replace this with the current user's role

return (

<Router>
<Switch>
{routes.map(({ path, component, exact, acl }) => (
<Route
key={path}
path={path}
exact={exact}
render={() => withAccessControl(component, acl)(currentUserRole)}
/>

))}

</Switch>
</Router>
);

};



export default App;

This code defines a higher-order function withAccessControl which takes in two arguments - a React component and an ACL (Access Control List) configuration.


The function returns a function that takes in a Role parameter, which represents the current role of the user.


The returned function checks the ACL configuration for the current user’s role and returns the corresponding decision. If the decision is to ALLOW, the original component is returned. If the decision is to REDIRECT, a Redirect component from the react-router-dom package is returned with the to prop set to the specified meta value. If no decision is found or the decision type is not recognized, null is returned.


The App component uses the withAccessControl function to wrap the components specified in the routes array. For each route, a Route component from the react-router-dom package is rendered with the path, component, exact, and ACL props passed in. The render prop is set to the withAccessControl function, which is called with the specified component and ACL configuration, and the current user's role. This ensures that only users with the appropriate permissions can access the specified routes.

Comment