Reputation: 820
I have a application where I have two logins one for superAdmin
and other for 'Admin'.
I have several pages One which is common (home page) both users have excess to that.
then I have several other pages which are some for admin
and others are for superAdmin
.
Now when I open my page I am trying to go '/' this route (My home route). What I am trying to do
Now If I am logged in as admin and admin user is typing some superAdmin
url in address bar I want that to be redirect to current admin route
Same goes for superAdmin
as well
both the user I want to restrict to excess each-others route
And if I am admin user or superAdmin user and trying to logged in and trying to excess authenticated route I should be redirect to home page
What I have done
I have created one component (Dynamic route) here I am checking what user is trying to do.
and in route.js in my routing file I am passing props as guest
,superAdmin
and admin
Dynamicroute.js code
I have created my context to store the user once they logged in
export default function Dynamicroute(props) {
const { user } = useAuthState(); // this I am getting from my context
console.log(user);
if (props.partner && !user) {
console.log('admin not logedin');
return <Redirect to="/admin" />;
} else if (props.admin && !user) {
console.log('superAdmin not loged in');
return <Redirect to="/superAdmin" />;
} else if (props.admin && user.role === 'admin') {
console.log('admin logedin');
return <Redirect to="/admin_home" />;
} else if (props.admin && user.role === 'superAdmin') {
console.log('super admin loged in');
return <Redirect to="/superadmin_home" />;
} else if (props.guest && user) {
console.log('guest');
return <Redirect to="/" />;
} else {
return <Route component={props.component} {...props} />;
}
}
My route.js
<DuynamicRoute exact path="/" component={Home} guest />
<DuynamicRoute path="/admin" component={loginAdmin} guest />
<DuynamicRoute path="/superAdmin" component={loginSuperAdmin} guest />
<DuynamicRoute path="/admin_home" component={admin_home} admin/>
<DuynamicRoute path="/superAdmin_home" component={superAdmin_home} superAdmin/>
Issue I am facing
I don't know what issue I am facing it is redirecting me to that route on login but content is not loading If I console something on that page I am not able to get that, the page is getting blank.
I am following this lecture from 25:00 timing
Edited
Please do check this
Edit
admin and super admin are going to be loged in different browsers, So just do not want admin to access super admin and vice-versa if they type in url each other's rout
Upvotes: 8
Views: 3766
Reputation: 2379
As this doesn't have an accepted answer yet I'll throw in the simple approach I use for adding / removing routes based on a condition.
The examples use Typescript but it should be relatively easy to strip the types out if needed.
Conditional Route Component:
This component has full typing of all the Route props plus an added prop called enabled.
When enabled is true the route will be rendered as normal, when it is false null will be returned.
// ConditionalRoute.tsx
import * as React from 'react';
import { Route, RouteProps } from 'react-router-dom';
interface ConditionalRouteProps extends RouteProps {
enabled?: boolean;
}
const ConditionalRoute: React.FunctionComponent<ConditionalRouteProps> = ({
enabled,
...routeProps
}) => {
if (!enabled) {
return null;
}
return (
<Route {...routeProps} />
);
};
export default ConditionalRoute;
Conditional Redirect Component:
This component has full typing of all the Redirect props plus an added prop called enabled.
When enabled is true the redirect will be actioned as normal, when it is false null will be returned.
// ConditionalRedirect.tsx
import * as React from 'react';
import { Redirect, RedirectProps } from 'react-router-dom';
interface ConditionalRedirectProps extends RedirectProps {
enabled?: boolean;
}
const ConditionalRedirect: React.FunctionComponent<ConditionalRedirectProps> = ({
enabled,
...redirectProps
}) => {
if (!enabled) {
return null;
}
return (
<Redirect {...redirectProps} />
);
};
export default ConditionalRedirect;
Using the Conditional Components:
Use the Conditional Routes / Redirects as you would their original base components except they will not come into effect unless the enabled property is true.
This also works with the Switch component as it will ignore components that return null.
// App.tsx
import * as React from 'react';
import { BrowserRouter, Switch } from 'react-router-dom';
interface AppProps {
authenticatedUser?: any;
}
const ConditionalRedirect: React.FunctionComponent<AppProps> = ({
authenticatedUser
}) => {
return (
<BrowserRouter>
<Switch>
{
// Always allow the unauthenticated error route
}
<Route
path="/error/401"
component={Unauthenticated}
/>
{
// If user is authenticated allow member route
}
<ConditionalRoute
enabled={!!authenticatedUser}
path="/member"
component={MemberHome}
/>
{
// If user is an admin allow admin route
}
<ConditionalRoute
enabled={authenticatedUser?.isAdmin}
path="/administration" component={AdminHome}
/>
{
// If user is an admin redirect uncaught route
// to administration route
}
<ConditionalRedirect
enabled={authenticatedUser?.isAdmin}
path="/"
to="/administration"
/>
{
// If user is authenticated redirect uncaught route
// to member route
}
<ConditionalRedirect
enabled={!!authenticatedUser}
path="/"
to="/member"
/>
{
// If user is not authenticated redirect uncaught
// route to unauthenticated error route
}
<ConditionalRedirect
enabled={!authenticatedUser}
path="/"
to="/error/401"
/>
</Switch>
</BrowserRouter>
);
};
export default App;
The main caveat of this solution is that any value in a condition has to be ready to be evaluated before rendering any of the routes. For example if you make an async request to check if a user is authenticated in the example App above, when the user is initially undefined before the async request returns it will redirect you to the 401 page.
Upvotes: 0
Reputation: 1449
For better management and development of the program along with the best practices, Do the Authorization in React.js as follows:
First: You need a class
for checking permissions
and routes/pages configs
like below:
class AppUtils {
static setRoutes(config) {
let routes = [...config.routes];
if (config.auth) {
routes = routes.map((route) => {
let auth = config.auth ? [...config.auth] : null;
auth = route.auth ? [...auth, ...route.auth] : auth;
return {
...route,
auth
};
});
}
return [...routes];
}
static generateRoutesFromConfigs(configs) {
let allRoutes = [];
configs.forEach((config) => {
allRoutes = [...allRoutes, ...this.setRoutes(config)];
});
return allRoutes;
}
static hasPermission(authArr, userRole) {
/**
* If auth array is not defined
* Pass and allow
*/
if (authArr === null || authArr === undefined) {
// console.info("auth is null || undefined:", authArr);
return true;
} else if (authArr.length === 0) {
/**
* if auth array is empty means,
* allow only user role is guest (null or empty[])
*/
// console.info("auth is empty[]:", authArr);
return !userRole || userRole.length === 0;
} else {
/**
* Check if user has grants
*/
// console.info("auth arr:", authArr);
/*
Check if user role is array,
*/
if (userRole && Array.isArray(userRole)) {
return authArr.some((r) => userRole.indexOf(r) >= 0);
}
/*
Check if user role is string,
*/
return authArr.includes(userRole);
}
}
}
export default AppUtils;
Second: You need Authorization component for handling routes like below:
import React, { Component } from "react";
import AppUtils from "utils/AppUtils";
import { matchRoutes } from "react-router-config";
import { withRouter } from "react-router-dom";
import AppContext from "context/AppContext";
class AppAuthorization extends Component {
constructor(props, context) {
super(props);
const { routes } = context;
this.state = {
accessGranted: true,
routes
};
}
componentDidMount() {
if (!this.state.accessGranted) {
this.redirectRoute();
}
}
componentDidUpdate() {
if (!this.state.accessGranted) {
this.redirectRoute();
}
}
static getDerivedStateFromProps(props, state) {
const { location, userRole } = props;
const { pathname } = location;
const matched = matchRoutes(state.routes, pathname)[0];
return {
accessGranted: matched
? AppUtils.hasPermission(matched.route.auth, userRole)
: true
};
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.accessGranted !== this.state.accessGranted;
}
redirectRoute() {
const { location, userRole, history } = this.props;
const { pathname, state } = location;
const redirectUrl = state && state.redirectUrl ? state.redirectUrl : "/";
/*
User is guest
Redirect to Login Page
*/
if (!userRole || userRole.length === 0) {
history.push({
pathname: "/login",
state: { redirectUrl: pathname }
});
} else {
/*
User is member
User must be on unAuthorized page or just logged in
Redirect to dashboard or redirectUrl
*/
history.push({
pathname: redirectUrl
});
}
}
render() {
// console.info('App Authorization rendered', accessGranted);
return this.state.accessGranted ? (
<React.Fragment>{this.props.children}</React.Fragment>
) : null;
}
}
// AppAuthorization.defaultProps = {
// userRole: [] // You can manage roles by redux or any state managements
// };
AppAuthorization.contextType = AppContext;
export default withRouter(AppAuthorization);
Third: You need authRoles file or remote for managing roles on client like below:
/**
* Authorization Roles
*/
const authRoles = {
admin: ["admin"],
superAdmin: ["superAdmin"],
user: ["user"],
onlyGuest: []
};
export default authRoles;
Forth: If you want to move forward with this logic, you have to implement the structure of your pages as follows:
src
|---pages
|---home
|---HomePage.jsx
|---HomePageConfig.jsx
|
|- The rest of the pages should be implemented in the same way
For example: When you want to implement a page that only the admin can see (admin home page config):
import React from "react";
import authRoles from "auth/authRoles";
export const AdminHomePageConfig = {
auth: authRoles.admin,
routes: [
{
path: "/admin",
exact: true,
component: React.lazy(() => import("./HomePage"))
}
]
};
Or the home page that everyone can see:
import React from "react";
export const HomePageConfig = {
routes: [
{
path: "/",
exact: true,
component: React.lazy(() => import("./HomePage"))
}
]
};
According to the example above, you can enter the auth prop
with the role
here, to restrict access to the page.
To get a closer look at this logic, I implemented it in the Codesandbox, which you can see via the link below:
Notice: The above demo needs to be more complete, and instead of storing user roles in the state, it is better to use state management packages (redux, ...) and also perform login operations through cookies.
Upvotes: 4
Reputation: 4893
The problem is that the DuynamicRoute component returns a Redirect in its top level, but Redirects don't work directly inside a Switch components. This is because a Redirect in a Switch would lead to an infinite redirection loop. To fix this, you should return a top level Route from your custom Route component, while handling the redirection logic between the Route tags.
Also, it's worth mentioning that some routes shouldn't be special protected routes, but regular landing pages, such as the home and login pages.
Here is a sample project based on your CodeSandbox solution: https://codesandbox.io/s/vigilant-feather-jbq4j
I made it so that superAdmin user can access admin level, but not the other way around. A lesser admin cannot access superAdmin content, without changing the active user to a superAdmin.
Here is an additional link with a slight modification to logic, for the use case where you would prefer that admin and superAdmin can't access each-other's protected pages: https://codesandbox.io/s/brave-haze-zsmn9?file=/src/ProtectedRoute.js
Upvotes: 0
Reputation: 632
Instead of creating a dynamic route, you can create a function that checks auth and redirects on entering the route.
const yourRouter = () => {
// yourAuthLogic
const routeAuth = (Component, props) => {
// redirect logic
// here you use the if/else branching based on auth state to redirect
// if no redirect
return (
<Component {...props} />
)
}
return (
<Router>
<Switch>
<Route path="/admin" render={() => routeAuth(component, props)} />
<Route path="/superAdmin" render={() => routeAuth(component, props)} />
</Switch>
</Router>
)
}
Upvotes: 0