How to implement multi role based authorization in React - javascript

I am working on application where I have different multi role ( admin, user,manager ) I want to protect route of admin from manager and general user also render UI based on user role . I tried but I am failed could someone please help me how to achieve my goal .
Example will be appreciated
Thanks

Please beware that this is not an optimal solution and only meant to give you some ideas.
Create a file RoleBasedRouting.jsx
function RoleBasedRouting({
component: Component, roles, ...rest
}) {
return (
<>
{ grantPermission(roles) && (
<Route
{...rest}
render={(props) => (
<>
<Component {...props} />
</>
)}
/>
)}
{
!grantPermission(roles) && (
<Route
render={() => (
<>
<Unauthorized /> // Unauthorized Page View (skippable)
</>
)}
/>
)
}
</>
);
}
Use it in your Router like this -
<Switch>
<RoleBasedRouting exact path="/admin" component={AdminPage} roles={['ROLE_ADMIN']} />
<RoleBasedRouting exact path="/user" component={UserPage} roles={['ROLE_USER']} />
<RoleBasedRouting exact path="/manager" component={ManagerPage} roles={['ROLE_Manager']} />
...............
</Switch>
In grantPermission function, check if the logged in user has the required roles. Sample -
export const grantPermission = (requestedRoles) => {
const permittedRoles = JSON.parse(localStorage.getItem('userRoles'));
// in case of multiple roles, if one of the permittedRoles is present in requestedRoles, return true;
return false;
};
To render UI conditionally, you can do basically the same thing. Write a component UnlockAccess -
const UnlockAccess = ({ children, request }) => {
const permission = grantPermission(request); // request = ['ROLE_ADMIN'] / ['ROLE_USER'] / ['ROLE_MANAGER']
return (
<>
{permission && children}
</>
);
};
Now, Use UnlockAccess component in the Dashboard page like this -
<Dashboard>
<UnlockAccess request={['ROLE_ADMIN']}>
<>
{/*Write code/components for Admin Dashboard*/}
</>
</UnlockAccess>
<UnlockAccess request={['ROLE_USER']}>
<>
{/*Write code/components for User Dashboard*/}
</>
</UnlockAccess>
<UnlockAccess request={['ROLE_MANAGER']}>
<>
{/*Write code/components for Manager Dashboard*/}
</>
</UnlockAccess>
</Dashboard>

You should create different route components specifically for all roles, for example, AdminRoute, UserRoute, etc and in these components, you can check the weather login person is admin or a normal user.
or create a general route component and pass role and path there as props

You can use a HOC to check if the user accessing the route is allowed to access, i.e. if user is admin who is trying to access the admin dashboard is admin. If not then can redirect him to wherever you like.
export default function withAuth(WrappedComponent) {
const auth = (props) => {
return (
localStorage.getItem("userRole") === "admin" ?
<WrappedComponent {...props} /> :
<Redirect to = {{
pathname: "/protected/login"
}} />
)
}
return auth;
}
Or you can maintain a features array in localStorage with features you wanna give access to your user.

Related

I am using firebase for user auth but when i try to write a statement to show one page when its logged in and not i get a hook error and it wont show

all the imports and exports work.
I took them out of the post because my words to code ratio is too low to post.
...
function App() {
const user = useAuthState(auth)
return (
<>
<Chat />
if (user ?) {
<Chat />
} else {
<SignIn />
}
</>
);
}
export default App;
I also tried using
...
function App() {
const user = useAuthState(auth)
return (
<>
{user ? <Chat /> : <SignIn />}
</>
);
}
export default App;
I still get a hook error.
yet when I remove const [user] = useAuthState(auth)
and replace {user...} with just <Chat /> it works and shows up with no error
/>}
there was a react error. I initialized two react apps in the beginning instead of one so it caused this hook error.

React Router. Why, when changing the route, the component is rendered 2 times, which causes 2 requests to the server?

I am using ReactTransitionGroup with ReactRouter.
The goal is to reroute smoothly from one component to another. The problem - is that the component is rendered twice.
An example of a component (view) that renders twice
I am using the console to check. You might say that this is not critical. But, the problem is that because of this problem, 2 requests go to the server (one extra). Therefore, it is desirable for me to get rid of this bug.
This is the component itself - the switch
When switching a route, the console issues logs twice
I need to figure out why the side effect is being called twice. If there is not enough information, then write comments. I will try to answer as quickly as possible.
Here's an example from the documentation. I have achieved this effect, but the problem has already been described.
UPD: I remember very well that once it worked like a clockwork. But, probably, I myself did not notice that I changed something, which led to this problem.
UPD: If you need a code, then please, the required elements:
const TabList = ({ tabs }) => {
return (
<nav className="p-my-company__tabs">
{tabs.map(({ to, label, id }) => (
<NavLink to={to} key={id}>
<div>{label}</div>
</NavLink>
))}
</nav>
);
};
const TabViews = ({ tabViews }) => {
const location = useLocation();
return (
<div className="p-my-company__views">
<TransitionGroup>
<SwitchTransition mode="out-in">
<CSSTransition
key={location.pathname}
classNames={{
enter: 'move-enter',
enterActive: 'move-enter-active',
exit: 'move-exit',
}}
timeout={100}>
<Switch>
{tabViews.map(({ path, Component, id }) => (
<Route path={path} render={() => <Component />} key={id} />
))}
</Switch>
</CSSTransition>
</SwitchTransition>
</TransitionGroup>
</div>
);
};
<div className="p-my-company__panel">
<TabList
tabs={[
{ to: ROUTES.COMMON_INFO, label: 'Общая информация', id: 1 },
{ to: ROUTES.SHOWCASE, label: 'Моя витрина', id: 2 },
]}
/>
<TabViews
tabViews={[
{ path: ROUTES.COMMON_INFO, Component: CommonView, id: 1 },
{ path: ROUTES.SHOWCASE, Component: ShowCaseView, id: 2 },
]}
/>
</div>
const ShowCase = () => {
useEffect(() => {
console.log(2);
}, []);
return <div>ShowCase</div>;
};
Looks like the Switch component from React Router and React Transition Group don't work well together. The docs recommend avoiding the usage of the Switch component and passing a function to the Route's children prop. Since the function will be called regardless of whether there is a match or not, you can conditionally render Component if there's one.
<>
{tabViews.map(({ path, Component }) => (
<Route exact path={path} key={path}>
{({ match }) => (
<TransitionGroup>
<SwitchTransition mode="out-in">
<CSSTransition
in={match != null}
classNames={{
enter: 'move-enter',
enterActive: 'move-enter-active',
exit: 'move-exit',
}}
timeout={100}
unmountOnExit
key={location.pathname}
>
<div className="page">{match && <Component />}</div>
</CSSTransition>
</SwitchTransition>
</TransitionGroup>
)}
</Route>
))}
</>

How to display a component when certain condition true using react and javascript?

The layout component is rendered on all pages.
I want to achieve the following
in /items page
*Layout component is displayed if the user is admin
* Layout component not displayed if the user is non-admin
below is my code,
function Main() {
const isAdmin = getUser();
return(
<Switch>
<Route
exact
path="/items"
render={routeProps => (
<Layout>
{isAdmin ? <Items {...routeProps} />: <NotFound/>}
</Layout>
)}
/>
<Route
exact
path="/home"
render={routeProps => (
<Layout>
<Home {...routeProps} />
</Layout>
)}
/>
</Switch>
);
}
const Layout: React.FC = ({ children }) => (
<>
<TopBar />
{children}
<BottomBar />
</>
);
As you see from the above code, the Layout component is displayed in all pages and is used as a wrapper for other routes too like for /home
Now I don't want the Layout component to be displayed only in /items page if a user is not admin
What I have tried?
const Layout: React.FC = ({ children }) => {
const history = useHistory();
const isItemsPath = history.location.pathname.includes('/items');
const isAdmin = getUser();
return (
<>
{!isItemsPath && <TopBar />
{children}
{!isItemsPath && <BottomBar />
</>
);
}
But this will not display TopBar and BottomBar if the items page even if the user is admin. how can I modify the condition
such that TopBar and BottomBar are displayed in all pages except items page if not admin.
could someone help me with this? thanks.
};
In your layout component you can use conditional rendering. We can check if the page is isItemsPath first, if it is items path and user is not admin then we do not show the Topbar and BottomBar, for all other pages we show them
const Layout: React.FC = ({ children }) => {
const history = useHistory();
const isItemsPath = history.location.pathname.includes('/items');
const isAdmin = getUser();
return !(isItemsPath && !isAdmin) ?
<>
{children}
</> : (
<>
<TopBar />
{children}
<BottomBar />
</>
);
}
What about you change the condition in Route?
<Route
exact
path="/items"
render={routeProps => (
{isAdmin ? <Layout>
<Items {...routeProps} />
</Layout>
: <NotFound/>}
)}
/>
If I understand correctly, you might be looking for something along the lines of this:
const Layout: React.FC = ({ children }) => {
const history = useHistory();
const isItemsPath = history.location.pathname.includes('/items');
const isAdmin = getUser();
return (
<>
{(!isItemsPath || isAdmin) && <TopBar />
{children}
{(!isItemsPath || isAdmin) && <BottomBar />
</>
);
Then you should be able to remove your isAdmin condition in your Main component.
You have some choice to doing this, but I think the best way is using HOC if you have to repeat checking the user is admin or not, pass your component to HOC and in HOC component check if a user is an admin or not. You can use this HOC component for all of your components. In HOC component use conditional rendering. Something like this :
function checkAdmin(WrappedComponent, selectData) {
const isAdmin = getUser();
render() {
return ({isAdmin} ? <WrappedComponent /> : <div></div>)
}
}

How to redirect to some page if condition is true using react?

i want to redirect to page "/items" if isAdmin true.
below is my code,
function Child() {
const isAdmin = getUser();
return isAdmin ? (
<Confirm />
) : (
<NotFound />
);
}
As seen in above code, if isAdmin true then i render confirm and if isAdmin false i am rendering NotFound.
But this will render NotFound within Parent component.
Instead i want to redirect to NotFound. How can I do it? Could someone help me with this. Thanks.
EDIT:
what i have tried?
return isAdmin ? (
<Confirm />
) : (
<Route component={NotFound} />
);
But this still renders in same page.
i have used the same code in main component like below,
function Main() {
return (
<Switch>
<Route
exact
path="/home"
render={routeProps => (
<Layout>
<Home {...routeProps} />
</Layout>
)}
/>
//other routes here
<Route component={NotFound}/> //if none of the urls match then //renders notfound
</Switch>
);
}
how do i use something similar. thanks.
if you're using react-router-dom react-router you can use the code like
i want to redirect to page "/items" if isAdmin true.
below is my code,
function Child(props) {
const isAdmin = getUser();
return isAdmin ? (
props.history.push('/confirm')
) : (
props.history.push('/notFound')
);
}
you need to have routes declared in the path /confirm & notFound in your routes.js or any where route is declared
You can use Redirect from react-router-dom.
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = () => {
const isAdmin = getUser();
return (
<Route render={isAdmin ? (
<Redirect to='/confirm' />
) : (
<NotFound />
)
} />
)
}
You can similarly use a Router Switch statement. Implementation can be found in the react router doc: https://reactrouter.com/web/api/Switch
Alternatively, you may use history.push('/404'). You can find more info under this question: How to push to History in React Router v4?
const Child = (props) => {
const isAdmin = getUser();
return (props.history.push(isAdmin ? '/confirm' : '/notFound');
}
You need to use history.push.

How to pass props to nested routes in React Router 4?

I have a component tree like this:
-App
--UserList
---UserItem
---UserItem
---UserItem
--User
I'm not being able to pass user data from UserItem to User. This is what I have:
App.js
export default class App extends Component {
state = { users: [] }
componentDidMount() {// fetch and setState}
render() {
return (
<BrowserRouter>
<Route
exact
path="/"
render={() => <UserList users={this.state.users} />}
/>
</BrowserRouter>
)
}
}
UserList.js
export default function({ users }) {
return (
<div>
{users.map(user => (
<UserItem user={user} key={`${user.id}`} />
))}
</div>
)
}
This is where the problem is: I want to pass the data from the parent component to the child User component, instead of having to fetch the user data from the API again.
UserItem.js
export default function({ user }) {
return (
<div>
<Link to="/user">{user.name}</Link>
<Route path={`/user/${user.name}`} render={() => <User user={user} />} />
</div>
)
}
I'm not sure what you're trying to implement here. Your app renders the UserList when then route is /. The UserList renders a UserItem component for each user in the array. Each UserItem simply renders a route specific to every user, which will render the User component if that route is triggered.
But if I'm not mistaken, the UserList will not be rendered if the route is anything but /, so if someone accesses user/..., the inner routes won't actually exist.
Essentially, this app will not render anything.
If you remove the exact keyword from the route in App, I think you'll get the result you are looking for. In this case, opening /user/<USER_NAME> will render the User element for that user.
Your question is regarding passing props into a component through a route, and the mechanism you've used is correct.
<Route path={...} render={() => <User user={user} />} />
This is actually right. See the code linked below. On changing the route to /user/User1, you'll see the name of "User1" rendered in the app.
See the working code here: https://codesandbox.io/s/18w3393767
You should use this.props.users in the UserItem component
i'm not sure but could you pass props like below, here i pass props to render and then to User Component
<Route path={`/user/${user.name}`} render={(props) => <User user={user} {...props} />} />
export default function({ users }) {
return (
<div>
{ this.props.users.map(user => (
//mistake here this.props.users.map not users.map
<UserItem user={user} key={`${user.id}`} />
))}
</div>
)
}

Categories