User role based routes - reactjs

I'm using react for my frontend
I'm having a login, forgot password in my routing
Based on user type I need to include a few sets of routes
Ex
User Type
Student:
Dashboard, Profile, View Attendance
Teacher :
Dashboard, Profile, add Attendance, Student Profile
After login then only I know user type.
If I load all the routes while rendering the app component, unwanted routes are loaded.
So the student can use the Teacher component. This might leads to problems.
How to rid of this problem

You could use react-authorization to wrap the component in the route and allow only users in specific roles to view it. You can also define an error component which would be displayed to an unauthorized user. Here's an example (in TypeScript):
const AdministrationRoute: React.FC<AdministrationRouteProps> = props => {
const accessDeniedError = <div className="ml-3 mr-3"><Alert
color="danger">Access denied!</Alert></div>;
return <IfGranted expected="ROLE_ADMIN" actual={props.user.roles} unauthorized={accessDeniedError}>
<Users/>
</IfGranted>;
};
And then use the component in a route as usual:
<Route path={Routes.administration.path} component={AdministrationRoute}/>
DISCLAIMER: I am the author of the react-authorization library.

Related

How to secure a component in Reactjs?

I have created two React components, Login, and Secure. Login hosts the FacebookLogin component found in this package: https://www.npmjs.com/package/#greatsumini/react-facebook-login, and I get a successful response from Facebook by the login attempt when the button is pressed. This is supposed to navigate to the Secure page when successful.
The problem is that I can just navigate directly to the Secure component in the URL, (I'm using react-router-dom package for this), but I want any attempt to navigate to a secure page to be redirected to the Login page.
How can I do this?
According to that component's documentation, you can get the login status in code:
FacebookLoginClient.login((res) => {
// "res" contains information about the current user's logged-in status
});
So in any component that needs to know the status, simply reference the library:
import { FacebookLoginClient } from '#greatsumini/react-facebook-login';
And use that FacebookLoginClient.login to check the user's logged-in status. If the status doesn't meet the criteria you define, redirect the user. (For example, you can check this in a useEffect when the component first loads, or perhaps on every render if you're paranoid about it.)
For more detail on rendering the target component, since useEffect occurs after the initial render, you can rely on additional state to check. For example, consider this structure:
const [canRender, setCanRender] = useState(false);
useEffect(() => {
FacebookLoginClient.login((res) => {
// check the status and call "setCanRender" accordingly
});
}, []);
if (!canRender) return <>Checking authentication...</>;
// your existing return for the component goes here
The idea here is to default the component to a kind of "loading state" and only render the "secure" content after the authentication has been verified.

React-Native: Deep Linking with required fetch Call in HomeScreen

I'm new on react-native and deep linking. I have a react-native App with BottomBar and StackNavigator.
First Tab "Stöbern" with First StackScreen HomeScreen has a fetch Call in ComponentDidMount for renew the session Token und set a variable for "isLoggedIn". For now, i don't have Deep Linking. For now the Startscreen is always HomeScreen with this fetch call to renew the token and check if token is valid, then set it to "isLoggedIn".
HomeScreen is a public screen, Favorite is a member screen.
Now i try deep linking.
My linking.js:
const config = {
screens: {
Home: {
path: 'home',
screens:{
Stöbern: {
path: 'stöbern',
screens: {
HomeScreen: {
path: 'home',
}
}
},
Favoriten: {
path: 'favorite',
screens: {
FavoriteScreen: {
path: 'favorite',
}
}
}
}
In StackNavigator I have a check module:
<HomeStack.Screen name="FavoriteScreen" component={RequireAuthentication(FavoriteScreen, global.isLoggedIn)} options={{ headerShown: false }} />
If my App is open and try:
npx uri-scheme open demo://app/home/favorite/favorite --android
it works fine, because the variable IsLoggedIn is set and im routing to favoriteScreen.
If my App is closed/killed and try:
npx uri-scheme open demo://app/home/favorite/favorite --android
the logic dont go thru HomeScreen with fetch Call to set IsLoggedIn and deep linking goes to the Login Screen. This is wrong, because im logged in.
If I move the fetch call to check the token and set the variable in App.js it still doesn't work. Fetch call is calling, but the response is to late and I'm routing to login Screen.
My Question:
what is the best way for deep linking and a fetch call to check token and set a variable for "isLoggedIn"?
Another call for renew token in FavoriteScreen? But then it calls also for non deep linking calls.
What I want:
User clicks on a deep link for favoriteScreen -> open the App -> do a fetch call for renew token and set global.isLoggedIn to True -> go to favoriteScreen
I'm also trying to go always over the HomeScreen. But this doesn't work if the App is open, because the ComponentDidMount method is not calling in this case.
There're 2 scenarios:
Best-case: the app is launched and ready and the user is authenticated and s/he can safely deep-link to any protected screen.
Worst-case: app recently killed or closed, it's required to re-authenticate and validate user token which is an asynchronous operation and takes a while which makes a deep-linked fallback to the login screen.
1. Identify navigation triggered by deep linking
// First, you may want to do the default deep link handling
// Check if app was opened from a deep link
const url = await Linking.getInitialURL();
if (url != null) {
return url;
}
console.log(url)
// url contain deep linking URL
While navigation is triggered by deep linking, you need to save url in a global store like Context API or Redux. url will be needed later after getting a new token.
2. Determine whether the user needs to re-authenticate
For the worst-case scenario, you will need to authenticate the user by silently validating the old token in the background or force to authenticate manually with a login form.
3. Navigate to a deep-linked screen
At this user has been authenticated or token validated, we need to deep-link to the user destination screen.
Early, we saved deep link url in global store, we need to link to the corresponding screen. Unfortunately, url has a web-based routing structure which is not how React navigation routing works.
We need to convert web routing to React navigation routing. Below is a minified routes mapping:
const routes map = {
demo://app/home/favorite/favorite:"favorite",
demo://app/home/stöbern/home:"home",
}
With this routes map, you can use navigation.navigate(map[url]) and simply navigate to that deep-linked.
This's my opinionated brute-force solution, fellow developers should come with a lean and better solution.

How to handle user permission base on role?

I'm creating an Admin Dashboard with React, and manage access base on user role (Admin, Client and User). Each role will have permission like:
Admin: can view Dashboard and CRUD posts
Client: can view Dashboard and create and read posts
User: can't view Dashboard and only read posts
Is there a way to implement this or npm package that supports with role n permission like this?
thanks!
this is demo code
https://codesandbox.io/s/hardcore-shape-6mvos?file=/src/App.tsx
In your case to handle react rendering your best chose is NextJs
By NextJs you can controller your rendering page before sending to your client
I deal with this every day. I have a component called Permission. It takes in the name of the permission(s) as a prop and checks to see if the user (stored in redux) has those permissions. If so, it returns the children of the Permission component.
Permission.js:
const Permission = ({ allowedPermissions, children }) => {
function isVisible() {
let visible = true;
allowedPermission.forEach((permission) => {
if (!user.permissions.includes(permission) {
visible = false;
}
});
return visible;
}
return isVisible() ? children : null;
}
Usage, which can include being wrapped around react-router switch/route statements:
<Permission allowedPermissions=["admin"]>
<Button onClick={() => deletePost()}>Delete</Button>
</Permission>

How to create dynamic route in gatsby

I have setup gatsby project using this link. It is working correctly.
Now I know how to create route by defining the component inside the pages folder. But now I have a new challenge I need to create one dynamic route so that I can pass my id in it (Just like reactjs).
<Route path: "/path/:id"/>
How do I do that in gatsby?
You have to explicitly tell gatsby that a path should be dynamic. From the docs:
// gatsby-node.js
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/app/)) {
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}
and then you can use dynamic routing in src/pages/app.js
import { Router } from "#reach/router"
const SomeSubPage = props => {
return <div>Hi from SubPage with id: {props.id}</div>
}
const App = () => (
<Layout>
<Link to="/app/1">First item</Link>{" "}
<Link to="/app/2">Second item</Link>{" "}
<Router>
// ...dynamic routes here
<SomeSubPage path="/app/:id" />
</Router>
</Layout>
)
export default App
Everything that goes to /app/* will be handled dynamically now. You should find your id as usual in the props.
Have a look at their authentication example https://github.com/gatsbyjs/gatsby/tree/master/examples/simple-auth
You can use square brackets ([ ]) in the file path to mark any dynamic segments of the URL. For example, in order to edit a user, you might want a route like /user/:id to fetch the data for whatever id is passed into the URL.
src/pages/users/[id].js will generate a route like /users/:id
src/pages/users/[id]/group/[groupId].js will generate a route like /users/:id/group/:groupId
Reference: https://www.gatsbyjs.com/docs/reference/routing/file-system-route-api#creating-client-only-routes
You can use gatsby-plugin-create-client-paths. It uses matchPath. For more info check
https://www.gatsbyjs.org/docs/gatsby-internals-terminology/#matchpath
https://www.gatsbyjs.org/packages/gatsby-plugin-create-client-paths/
This answer is Super late, but for anyone in the future who is faced with this problem, I have a simpler solution.
In Gatsby terms it's called a Splat Route.
For examples, If you want some page "domain.com/profile/[id]", where id can be any number, which will be used to display different data inside the website, you should name your page as [...id].
Now inside the page you can access this id as
const ProfilePage = (props) => <div>This page is for id number {props.params.id}</div>
Note: Don't miss the 3 dots, that is what signifies a splat route in gatsby.

Best way to reload a ReactJS component to read from localStorage

Context: I have a <Header /> component that has a button which redirects the user to a <SignIn /> component. The <Header /> component is always displayed, however when the user is signed in, the Login button disappears and replaced by the user's name.
I will not go in detail about the authentication mechanism, but let's just say it is based on a JWT that is stored in localStorage. I have the following piece of code in <Header /> in order to read the cookie and determine whether to show the login button or not:
componentDidMount() {
const token = localStorage.getItem("token");
if (token) {
this.props.fetchUser();
}
}
...
However this only works on a browser refresh or if I use the native JS window.reload(). If I use react router redirections, the Login button in the <Header /> component is not refreshed and still displays (although the cookie is set).
I am wondering what is the best practice, and avoiding the whole page refresh...
So it is super pseudo code, but hopefully that would be enough :)
class App {
const handleLoggedIn = () => {
const token = readFromLocalStorage()
if (token) {
this.setState({token})
}
}
<Header token={this.state.token} />
<Login onLogin={this.handleLoggedIn}
}
class Login {
// After loging in and setting your token to your local storage:
this.props.onLogin()
}
class Header {
if (this.props.token)
<User />
else
<Login />
}
So once you are logged in and set your token to the local storage, you just call the parent function handleLoggedIn which will try to retrieve the token from your local storage. If it is set, it will re-set it as your App state, which will refresh your Header component as one of its props has been updated
(But that mainly depends on how you've implemented the rest of your code and how easily you can access the parent function from your login flow as pointed by Dubes)
Two possible scenarios I can think of:
Scenario 1
You've someway of hooking into the flow when user signs in. For e.x. maybe your auth system is setting a success flag in header/query or perhaps the component which writes the jwt to the localstorage is in your control/tree.
In that case, you should be derive that information and be able to set a prop which your header component can use.
Scenario 2
You don't have any way of hooking into your auth workflow and can only read the localstorage item. For e.x. a user has signed on with SSO and you want to reflect that without having to refresh the entire page.
I think you can use a setInterval to read the state of the cookie and set a state in your header component. As long as you are not doing intensive calculations in your setInterval code, it should not be an overhead. Do remember to clearInterval on unmount.
Take a look at this example in official docs for inspriration.

Resources