How to use role based authentication in react-router-dom? - javascript

I have seen there are many answers for this question. But here, my question and my existing code is different. I'm using following NPM module to route in my react application.
import { Route, Switch, BrowserRouter } from 'react-router-dom';
And this is the routing file of my application.
https://gist.github.com/chanakaDe/241daafbc94df8543bced3695c7b7169
I want to use role base authentication system on this. All the roles are saved in the local storage and i need a way to check those when use goes to a route. I'm new to react. Please help me how to solve this.
Do I need to use separate file to get all the roles from local storage or can we do that inside the routing file ? Please advice

You can make Route custom components to handle the type of route. For example, if you have two Routes:
1) Admin Routes
2) Host Routes
then you will make one Component that will take the role as props and then using this component you will return the which route you need to render.
Small example is shown below:
class ResolveRoute extends React.Component {
render () {
const role = this.props.role
return (
{
() => {
if (roles === "admin") {
<Route ...this.props />
}
else if (roles === "host") {
<Route ...this.props/> // Render the routes that have host roles.
}
else {
<Redirect to="/" /> // or Not found error page.
}
}
}
)
}
}

Related

Prevent routing while user manually changes url in the browser - ReactJS

const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
{
path: "/main",
element: (
<AdminAuth redirectTo="/profile">
<Main />
</AdminAuth>
),
},
])
import React from "react";
import { Link, Navigate } from "react-router-dom";
import { isAutheticated } from "../auth";
export const AdminAuth = ({ children, redirectTo }) => {
let auth = isAutheticated().user_Role;
return auth === "admin" ? children : <Navigate to={redirectTo} />;
};
I want to prevent routing while user manually changes url in the browser. I have the "/main" as an admin route, which I'm protecting, but the issue starts when user changes his role in the local storage and tries to access `"/main". I want to prevent user from manually changing the route in their url or show error if they change manually.
EDIT: I'm protecting my route in the backend, but in the frontend I don't user to even access this.
Considering that all code on the client can be changed by the user, setting up protected routes like yours, should only be for a better user experience. You cannot and should not depend on client logic to protect data from non-admin users. This has to be done on the backend/server.
Therefore, using local storage as your business logic for identifying an admin user or not, would be highly critical, since everyone could change that. Instead it should be done by some token, for exmaple JWT (Json Web Token) using a authentication provider like AWS Cognito or similiar, or even building your own server side authetication logic.
Simply by sending prop from previous page, you can control and simply send the user back to the proper page.
I want to prevent user from manually changing the route in their url
or show error if they change manually.
There's simply just no way of knowing from inside the app how exactly the URL changed in the address bar. When a user does this the browser completely reloads the page, so the entire React app is mounted fresh. react-router can then only read what the current URL is to handle route matching and rendering.
If you are certain that you don't trust the frontend client then a solution here would be to validate the user against the backend on every route/component you want to protect each time they are navigated to.
Example implementation:
import React from "react";
import { Navigate } from "react-router-dom";
import { authService } from "../auth";
export const AdminAuth = ({ children, redirectTo, role }) => {
const [userRole, setUserRole] = React.useState();
React.useEffect(() => {
authService.validateUser()
.then(user => {
setUserRole(user.user_Role);
});
}, []);
if (userRole === undefined) {
return null; // or loading indicator/spinner/etc
}
return userRole === role
? children
: <Navigate to={redirectTo} replace />;
};
const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
{
path: "/main",
element: (
<AdminAuth redirectTo="/profile" role="admin">
<Main />
</AdminAuth>
),
},
]);
Note that checking with the backend for each and every route transition will incur a network cost and slow the frontend UI page transitions down. Depending on the sensitivity of the content this may be an acceptable cost. That's a decision only you or your product owners can make.

AuthStatus is not changing from configuring state using amplify ui react

I'm using AWS amplify UI react to create the authentication flow in the react application. I followed the document and made the navigation flow using the doc below.
https://ui.docs.amplify.aws/react/guides/auth-protected.
However, after logging in, I was able to see the login page flickering each time when I access any other route. To solve this, I followed one of the answers from the below question.
Flicker of login page on Authentication - useAuthenticator - React - Amplify Auth.
Unfortunately now, the page is always stuck in the "configuring" state and the authStatus never getting changed at all. How do I handle this scenario to automatically redirect to the login page if not authenticated and not show the login page each time user refreshes the page?
NOTE: This question is related to amplify-ui react with Authenticator.provider component.
RequireAuth.tsx - all routes are wrapped inside this
import { useLocation, Navigate } from "react-router-dom";
import { useAuthenticator } from "#aws-amplify/ui-react";
import PageLoader from "../../components/loader/page-loader";
export function RequireAuth({ children }: any) {
const location = useLocation();
const { authStatus, user } = useAuthenticator((context) => [
context.authStatus,
]);
console.log("authStatus:::", authStatus);
console.log("user:::", user);
if (authStatus === "authenticated") {
return <>{children}</>;
} else if (authStatus === "unauthenticated") {
return <Navigate to="/login" state={{ from: location }} replace />;
} else if (authStatus === "configuring") {
return <PageLoader />;
} else {
return <Navigate to="/login" state={{ from: location }} replace />;
}
}
And few routes in appRoutes.
<Route
path="/"
element={
<RequireAuth>
<AppLayout />
</RequireAuth>
}>
<Route
index
element={
<RequireAuth>
<Home />
</RequireAuth>
}
/>
Unfortunately, this appears to be a known bug with <Authenticator.Provider> and <Authenticator/>.
Until the bug is fixed, there is a known workaround that involves always including the <Authenticator/> component within the active dom structure and then hiding it using CSS. It's pretty terrible, but, worked for me:
[data-amplify-authenticator] {
display:none;
}
In the future, I am planning to write a custom UI and handle the authentication in my backend before dropping these components altogether.

React-Redux: Best way to restrict user access to certain pages if they are not logged in

I am new to React/Redux, and I am working on a React application. I have been learning the basic concepts of Redux such as Store, Actions, Reducers, Middleware and the connect() function.
However, for some of the pages in my application, I want to allow only users that are logged in to be able to access them, otherwise they are redirected back to the home page of the application.
I was wondering what is the best method to be able to achieve this task? I have heard that React Router can be used to do this, but are there any better ways? Any insights are appreciated.
As you suggested the router already, using it this works neatly n safely supposing the user is only set in the redux state, when properly authenticated in the login:
import { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom':
const mapStateToProps = (state) => {
return {
user: state.user
}
}
class MyPageWrapper extends Component {
componentWillMount() {
if(!this.props.user) this.props.history.push('/login');
}
render() {
return (
<div>
<YourPage/>
</div>
);
}
}
export default connect(mapStateToProps)(withRouter(MyPageWrapper));

How to get params from url with React front end and express backend?

I'm building an react app with an express backend. I'm now adding socket io chat functionality to the front end. Everything works as expected but now I want to access params from the url to set the socket-io channel name.
I want the user to visit localhost:3000/foo and the react frontend to be able to access the foo parameter.
What's the best way to do this?
At the moment I am serving the static files like so:
app.use(express.static(`${__dirname}/../client/dist`));
I tried to add react-router-dom with the following code but it doesnt display the page at all:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom'
import App from './components/App.jsx';
ReactDOM.render(
<Router>
<Route path="/:id" component={App} />
</Router>,
document.querySelector('#app'));
Whenever I add something to the end of the url (I fi type in something other than ‘/‘ the page does not display. I get the error “cannot GET /foo”
I've also tried this but then the front end doesn't display either:
app.get('/:id', (req, res) => {
console.log('-----------', req.params.id)
})
My ultimate goal would be to only display the chat app when a user visits localhost:3000/chat/:channelId
If you are using react-router your approach is perfectly fine, you should be able to retrieve channel name from match parameter
const App = ({ match }) => (
<div>
<h2>You are listening to: {match.params.id}</h2>
</div>
)
Look at https://reacttraining.com/react-router/web/example/ambiguous-matches
By referring the answer provided by #mavarazy. You could write it in a class and get the parameter via props.
export default class User extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<h2>You are listening to: {this.props.match.params.id}</h2>
</div>
)
}

React-Router External link

Since I'm using React Router to handle my routes in a React app, I'm curious if there is a way to redirect to an external resource.
Say someone hits:
example.com/privacy-policy
I would like it to redirect to:
example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies
I'm finding exactly zero help in avoiding writing it in plain JavaScript at my index.html loading with something like:
if (window.location.path === "privacy-policy"){
window.location = "example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies"
}
Here's a one-liner for using React Router to redirect to an external link:
<Route path='/privacy-policy' component={() => {
window.location.href = 'https://example.com/1234';
return null;
}}/>
It uses the React pure component concept to reduce the component's code to a single function that, instead of rendering anything, redirects browser to an external URL.
It works both on React Router 3 and 4.
With Link component of react-router you can do that. In the "to" prop you can specify 3 types of data:
a string: A string representation of the Link location, created by concatenating the location’s pathname, search, and hash properties.
an object: An object that can have any of the following properties:
pathname: A string representing the path to link to.
search: A string representation of query parameters.
hash: A hash to put in the URL, e.g. #a-hash.
state: State to persist to the location.
a function: A function to which current location is passed as an argument and which should return location representation as a string or as an object
For your example (external link):
https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies
You can do the following:
<Link to={{ pathname: "https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies" }} target="_blank" />
You can also pass props you’d like to be on the such as a title, id, className, etc.
There isn’t any need to use the <Link /> component from React Router.
If you want to go to external link use an anchor tag.
<a target="_blank" href="https://meetflo.zendesk.com/hc/en-us/articles/230425728-Privacy-Policies">Policies</a>
It doesn't need to request React Router. This action can be done natively and it is provided by the browser.
Just use window.location.
With React Hooks
const RedirectPage = () => {
React.useEffect(() => {
window.location.replace('https://www.google.com')
}, [])
}
With React Class Component
class RedirectPage extends React.Component {
componentDidMount(){
window.location.replace('https://www.google.com')
}
}
Also, if you want to open it in a new tab:
window.open('https://www.google.com', '_blank');
I actually ended up building my own Component, <Redirect>.
It takes information from the react-router element, so I can keep it in my routes. Such as:
<Route
path="/privacy-policy"
component={ Redirect }
loc="https://meetflo.zendesk.com/hc/en-us/articles/230425728-Privacy-Policies"
/>
Here is my component in case anyone is curious:
import React, { Component } from "react";
export class Redirect extends Component {
constructor( props ){
super();
this.state = { ...props };
}
componentWillMount(){
window.location = this.state.route.loc;
}
render(){
return (<section>Redirecting...</section>);
}
}
export default Redirect;
Note: This is with react-router: 3.0.5, it is not so simple in 4.x
I went through the same issue. I want my portfolio to redirect to social media handles. Earlier I used {Link} from "react-router-dom". That was redirecting to the sub directory as here,
Link can be used for routing web pages within a website. If we want to redirect to an external link then we should use an anchor tag. Like this,
Using some of the information here, I came up with the following component which you can use within your route declarations. It's compatible with React Router v4.
It's using TypeScript, but it should be fairly straightforward to convert to native JavaScript:
interface Props {
exact?: boolean;
link: string;
path: string;
sensitive?: boolean;
strict?: boolean;
}
const ExternalRedirect: React.FC<Props> = (props: Props) => {
const { link, ...routeProps } = props;
return (
<Route
{...routeProps}
render={() => {
window.location.replace(props.link);
return null;
}}
/>
);
};
And use with:
<ExternalRedirect
exact={true}
path={'/privacy-policy'}
link={'https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies'}
/>
The simplest solution is to use a render function and change the window.location.
<Route path="/goToGoogle"
render={() => window.location = "https://www.google.com"} />
If you want a small reusable component, you can just extract it like this:
const ExternalRedirect = ({ to, ...routeProps }) => {
return <Route {...routeProps} render={() => window.location = to} />;
};
and then use it (e.g. in your router switch) like this:
<Switch>
...
<ExternalRedirect exact path="/goToGoogle" to="https://www.google.com" />
</Switch>
I had luck with this:
<Route
path="/example"
component={() => {
global.window && (global.window.location.href = 'https://example.com');
return null;
}}
/>
I solved this on my own (in my web application) by adding an anchor tag and not using anything from React Router, just a plain anchor tag with a link as you can see in the picture screenshot of using anchor tag in a React app without using React Router
Basically, you are not routing your user to another page inside your app, so you must not use the internal router, but use a normal anchor.
Although this is for a non-react-native solution, but you can try.
In React Router v6, component is unavailable. Instead, now it supports element. Make a component redirecting to the external site and add it as shown.
import * as React from 'react';
import { Routes, Route } from "react-router-dom";
function App() {
return(
<Routes>
// Redirect
<Route path="/external-link" element={<External />} />
</Routes>
);
}
function External() {
window.location.href = 'https://google.com';
return null;
}
export default App;
In React Route V6 render props were removed. It should be a redirect component.
RedirectUrl:
const RedirectUrl = ({ url }) => {
useEffect(() => {
window.location.href = url;
}, [url]);
return <h5>Redirecting...</h5>;
};
Route:
<Routes>
<Route path="/redirect" element={<RedirectUrl url="https://google.com" />} />
</Routes>
I think the best solution is to just use a plain old <a> tag. Everything else seems convoluted. React Router is designed for navigation within single page applications, so using it for anything else doesn't make a whole lot of sense. Making an entire component for something that is already built into the <a> tag seems... silly?
To expand on Alan's answer, you can create a <Route/> that redirects all <Link/>'s with "to" attributes containing 'http:' or 'https:' to the correct external resource.
Below is a working example of this which can be placed directly into your <Router>.
<Route path={['/http:', '/https:']} component={props => {
window.location.replace(props.location.pathname.substr(1)) // substr(1) removes the preceding '/'
return null
}}/>
I don't think React Router provides this support. The documentation mentions
A < Redirect > sets up a redirect to another route in your application to maintain old URLs.
You could try using something like React-Redirect instead.
I was facing the same issue and solved it using by http:// or https:// in React.
Like as:
<a target="_blank" href="http://www.example.com/" title="example">See detail</a>
You can use for your dynamic URL:
<Link to={{pathname:`${link}`}}>View</Link>
For V3, although it may work for V4. Going off of Eric's answer, I needed to do a little more, like handle local development where 'http' is not present on the URL. I'm also redirecting to another application on the same server.
Added to the router file:
import RedirectOnServer from './components/RedirectOnServer';
<Route path="/somelocalpath"
component={RedirectOnServer}
target="/someexternaltargetstring like cnn.com"
/>
And the Component:
import React, { Component } from "react";
export class RedirectOnServer extends Component {
constructor(props) {
super();
// If the prefix is http or https, we add nothing
let prefix = window.location.host.startsWith("http") ? "" : "http://";
// Using host here, as I'm redirecting to another location on the same host
this.target = prefix + window.location.host + props.route.target;
}
componentDidMount() {
window.location.replace(this.target);
}
render(){
return (
<div>
<br />
<span>Redirecting to {this.target}</span>
</div>
);
}
}
export default RedirectOnServer;
I am offering an answer relevant to React Router v6 to handle dynamic routing.
I created a generic component called redirect:
export default function Redirect(params) {
window.location.replace('<Destination URL>' + "/." params.destination);
return (
<div />
)
}
I then called it in my router file:
<Route path='/wheretogo' element={<Redirect destination="wheretogo"/>}/>
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
function App() {
return (
<Router>
<Route path="/" exact>
{window.location.replace("http://agrosys.in")}
</Route>
</Router>
);
}
export default App;
Using React with TypeScript, you get an error as the function must return a React element, not void. So I did it this way using the Route render method (and using React router v4):
redirectToHomePage = (): null => {
window.location.reload();
return null;
};
<Route exact path={'/'} render={this.redirectToHomePage} />
Where you could instead also use window.location.assign(), window.location.replace(), etc.
Complementing Víctor Daniel's answer here: Link's pathname will actually take you to an external link only when there's the 'https://' or 'http://' before the link.
You can do the following:
<Link to={{ pathname:
> "https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies"
> }} target="_blank" />
Or if your URL doesn't come with 'https://', I'd do something like:
<Link to={{pathname:`https://${link}`}} target="_blank" />
Otherwise it will prepend the current base path, as Lorenzo Demattécommented.
If you are using server-side rending, you can use StaticRouter. With your context as props and then adding <Redirect path="/somewhere" /> component in your app. The idea is every time React Router matches a redirect component it will add something into the context you passed into the static router to let you know your path matches a redirect component.
Now that you know you hit a redirect you just need to check if that’s the redirect you are looking for. then just redirect through the server. ctx.redirect('https://example/com').
You can now link to an external site using React Link by providing an object to to with the pathname key:
<Link to={ { pathname: '//example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies' } } >
If you find that you need to use JavaScript to generate the link in a callback, you can use window.location.replace() or window.location.assign().
Over using window.location.replace(), as other good answers suggest, try using window.location.assign().
window.location.replace() will replace the location history without preserving the current page.
window.location.assign() will transition to the URL specified, but will save the previous page in the browser history, allowing proper back-button functionality.
location.replace()
location.assign()
Also, if you are using a window.location = url method as mentioned in other answers, I highly suggest switching to window.location.href = url.
There is a heavy argument about it, where many users seem to adamantly want to revert the newer object type window.location to its original implementation as string merely because they can (and they egregiously attack anyone who says otherwise), but you could theoretically interrupt other library functionality accessing the window.location object.
Check out this conversation. It's terrible.
JavaScript: Setting location.href versus location
I was able to achieve a redirect in react-router-dom using the following
<Route exact path="/" component={() => <Redirect to={{ pathname: '/YourRoute' }} />} />
For my case, I was looking for a way to redirect users whenever they visit the root URL http://myapp.com to somewhere else within the app http://myapp.com/newplace. so the above helped.

Categories

Resources