I'm building a React JS app and I'd like to use AWS Amplify's AmplifyAuthenticator to easily protect routes I configured using react-router-dom. I've used Amplify's withAuthenticator in the past, but I want to ensure I have the flexibility to customize auth in the future...hence why I'm using AmplifyAuthenticator.
I've wrapped my protected routes in the <AmplifyAuthenticator> component and it seems to be working fine, but I wanted to make sure this is a solid approach. My code looks like something this:
import React, { useCallback, useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { AmplifySignOut, AmplifySignIn, AmplifyAuthenticator } from '#aws-amplify/ui-react';
import LandingPage from './components/LandingPage';
import ProtectedPage from './components/ProtectedPage';
import Navigation from './components/Navigation';
function App() {
return (
<Router>
<Navigation />
<Switch>
<Route path="/" exact component={LandingPage}/>
<Route path="/LangingPage" component={LangingPage}/>
<AmplifyAuthenticator>
<Route path="/protected" component={ProtectedPage}/>
</AmplifyAuthenticator>
</Switch>
</Router>
);
}
export default App;
Is this an efficient way to go about securing these routes? Thanks a lot!
wrap the Route into PrivateRoute:
// A wrapper for <Route> that redirects to the login
// screen if you're not yet authenticated.
function PrivateRoute({ children, ...rest }) {
let auth = useAuth();
return (
<Route
{...rest}
render={({ location }) =>
auth.user ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
https://codesandbox.io/s/react-router-redirects-auth-xb8qq?from-embed=&file=/example.js
Related
I'm creating a website, and I want the users to be directed to a specific page when they open the site. The page they are going to be directed to depends on if they already logged in. My problem is: the router doesn't work (user is not redirected to any page) and all that appears is a blank page. I've tried to get rid of the routes, but even though, I couldn't display anything on the index page. Maybe the problem is not even the router, but something else.
I never get any error messages. Here are the parts of the code, where I think the problem may be.
_app.js:
import React from 'react'
import { BrowserRouter as Router, Route } from "react-router-dom"
import Novidades from './lancamento'
import SignUp from './signup'
import Croosa from './croosa'
import { AuthProvider } from '../utils/auth'
import PrivateRoute from '../utils/PrivateRoute'
const App = () => {
return(
<AuthProvider>
<Router>
<div>
<PrivateRoute exact path='/lancamento' component={Lancamento} />
<Route exact path='/croosa' component={Croosa}/>
<Route exact path='/signup' component={SignUp} />
</div>
</Router>
</AuthProvider>
)
}
export default App
index.js:
import React from 'react'
import App from './_app'
export default function Home() {
return(
<App/>
)
}
And the PrivateRoute.js, which decides to which page the user is going to be redirected:
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthContext } from "./auth";
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
const {currentUser} = useContext(AuthContext);
return (
<Route
{...rest}
render={routeProps =>
!!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/signup"} />
)
}
/>
)
}
export default PrivateRoute
I would appreciate it if someone could point out my mistake(s).
Next.js uses a filesystem based routing structure.
You have a misunderstanding of how the _app.js file works. It's the parent component that is responsible for rendering the other components that get exported from other pages.
For example: if my _app.js file looks like this:
export default function App({ Component, pageProps }) {
return (
<div>
<p>This was injected by _app.js</p>
<Component {...pageProps} />
<div>
);
}
and my pages/index.js file looks like this:
export default function Hello(){
return <h1>Hello World!</h1>
}
With that setup if I visit the localhost:3000/ then the following will get rendered
<div>
<p>This was injected by _app.js</p>
<h1>Hello World!</h1>
</div>
What you did instead is in your _app.js you ignored the Component property that was passed and so on every page you visit the same content will be rendered. That is:
<AuthProvider>
<Router>
<div>
<PrivateRoute exact path="/lancamento" component={Lancamento} />
<Route exact path="/croosa" component={Croosa} />
<Route exact path="/signup" component={SignUp} />
</div>
</Router>
</AuthProvider>
The reason your index page is blank is because you didn't set up a route for / and so no component will be rendered by react router on that page. Regardless I suggest you stop using react router and start using the built in routing system with next.js
My react component should work as follows:
Check a global variable for error and if there is error redirect to home and unset the global error variable.
Otherwise it should render a which will use current page's location to redirect to correct location.
How should I go about doing this. The only way which somehow works is if I do conditional render and set global variable in render. Is there any better way?
In this example I'm using context api to share the state across all routes available.
For question No. 1 - use protected route. (Much simpler & neat)
predeclared path & component on <ProtectedRoute path="/anyPath" component={anyComponent}/>
if you want to have flexibility on setting the path yourself, then go to methods No. 2 below
in App.js:-
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { ErrorState } from "./contexts/ErrorState";
// import ProtectedRoute from "./comps/routes/ProtectedRoute";
import ProtectedRouteCustom from "./comps/routes/ProtectedRouteCustom";
import Home from "./comps/Home";
import Demo from "./comps/Demo";
import Dashboard from "./comps/Dashboard";
import "./style.css";
export default function App() {
return (
<ErrorState>
<Router>
<NavBar />
<Switch>
<Route exact path="/" component={Home} />
<Demo exact path="/demo" component={Demo} />
{/*} <ProtectedRoute exact path="/demo/dashboard" component={Dashboard} /> */}
<ProtectedRouteCustom path="/demo" />
<Route path="*" component={() => "Not Found"} />
</Switch>
</Router>
</ErrorState>
);
}
ProtectedRoute.js (emphasis more on Route render):-
import React from "react";
import { Redirect, Route } from "react-router-dom";
import { useError } from "../../contexts/ErrorState";
const ProtectedRoute = ({ component: Component, ...rest }) => {
const [errorState, errorDispatch] = useError();
const { error } = errorState;
return (
<Route
{...rest}
render={props => {
// you can use props.location.pathname to redirect user to the route path you have specified in App.js (see in console.log)
console.log(props);
// render Dashboard component if 'error' === false
if (!error) return <Component {...props} />;
// redirect to homepage if error === false
else
return (
<Redirect
to={{
// specify the path to redirect (if condition 'error' === true)
pathname: "/",
state: { from: props.location }
}}
/>
);
}}
/>
);
};
export default ProtectedRoute;
For question No. 2 - you can build your own custom protected route
path can be specified or set it yourself in ProtectedRouteCustom
App.js:-
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { ErrorState } from "./contexts/ErrorState";
// import ProtectedRoute from "./comps/routes/ProtectedRoute";
import ProtectedRouteCustom from "./comps/routes/ProtectedRouteCustom";
import Home from "./comps/Home";
import Demo from "./comps/Demo";
import Dashboard from "./comps/Dashboard";
import "./style.css";
export default function App() {
return (
<ErrorState>
<Router>
<NavBar />
<Switch>
<Route exact path="/" component={Home} />
<Demo exact path="/demo" component={Demo} />
{/*} <ProtectedRoute exact path="/demo/dashboard" component={Dashboard} /> */}
<ProtectedRouteCustom path="/demo" />
<Route path="*" component={() => "Not Found"} />
</Switch>
</Router>
</ErrorState>
);
}
custom protected route:- (emphasis more on Redirect rather than Route render)
import React from "react";
import { Redirect, Route } from "react-router-dom";
import { useError } from "../../contexts/ErrorState";
import Dashboard from "../Dashboard";
const ProtectedRouteCustom = ({ path }) => {
const [errorState, errorDispatch] = useError();
const { error } = errorState;
return (
<>
{error ? (
<Redirect to="/" />
) : (
<>
<Redirect from={path} to={path + "/dashboard"} />
<Route to={path + "/dashboard"} component={Dashboard} />
</>
)}
</>
);
};
export default ProtectedRouteCustom;
You can see the sandbox here for full working code.
Guidelines on how to use the sandbox code
Environment for No. 1:-
uncomment/enable:
import ProtectedRoute from "./comps/routes/ProtectedRoute";
<ProtectedRoute exact path="/demo/dashboard" component={Dashboard} />
comment/disable:
import ProtectedRouteCondition from "./comps/routes/ProtectedRouteCondition";
<ProtectedRouteCondition path="/demo" />
Environment for No. 2: run as it is
Generally all coder using your methods. If you checking React in Facebook Group. You can find correct answer ı think.
Im trying to use React Routing V6 for my project.
Currently im struggeling to make the authentication and routing itself to work.
the idea of my code is:
Not authenticated user:
redirect to /login with my login component. (only login component)
Authenticated user:
Load the gameComponent component, and the rest of links inside of gamecomponent, will load inside gameComponents div class named middleContentHolder
examples:
authenticated user:
visits url /crime -> loads gamecomponent, and within gamecomponent it loads the crime component.
visits url /test -> loads gamecomponent , and within gamecomponent it loads the SideBarRight component.
not authenticated user:
vitits url /crime -> not authenticated -> redirects to /login -> loads loginmodule only.
please note that in gamecomponent component, i do have links that will load within gamecomponent.
app.js will either load the gamecomponent, or redirect user to login if not auth.
app.js:
import React from 'react';
import logo from './logo.svg';
import './App.css';
import GameComponent from './gameComponent.jsx';
import { BrowserRouter as Router } from 'react-router-dom';
import { Routes, Route, Navigate, Outlet } from 'react-router-dom';
import Crime from './components/game/crime.jsx';
import PrivateRoute from './PrivateRoute';
import Login from './components/login/login.jsx';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<GameComponent />}>
<PrivateRoute isAuth={true} path="crime" component={Crime} redirectTo='/login'/>
</Route>
</Routes>
</Router>
);
}
export default App;
Gamecomponent:
import React, {Component} from 'react';
//import Component from 'react-dom';
import SideBarRight from './components/game/sideBarRight.jsx';
import SideBarLeft from './components/game/sideBarLeft.jsx';
import Crime from './components/game/crime.jsx';
import Login from './components/login/login.jsx';
import './gameComponent.css';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import { BrowserRouter} from "react-router-dom";
class GameComponent extends Component{
constructor() {
super();
this.state = {
userData: {
user: {cash:0, bank:0, weapon:'', username: 'test', locationname: 'Bankok',
defence: 0},
rankbar: {rankpercent: 50, rank: 'Mafia'},
}
}
}
render() {
return (
<div className="main">
<div className="sidebar left">
<SideBarLeft/>
</div>
<div className="middleContentHolder">
<Route path="/" element={<Crime />} />
<Route path="/test" element={<Crime />} />
<Route path="/crime" element={<Crime />} />
<Route path="/test" element={<SideBarRight UserData={this.state.userData} />} />
<div className="col-8">
<div className="content">
<div className="header"><span>Test...</span></div>
</div>
</div>
</div>
<div className="sidebar right">
<SideBarRight UserData={this.state.userData}/>
</div>
</div>
);
}
}
export default GameComponent;
PrivateRoute:(auth is just a dummy atm)
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Navigate } from 'react-router-dom';
import { useNavigate } from "react-router-dom";
import Login from './components/login/login.jsx';
import GameComponent from './gameComponent.jsx';
const PrivateRoute = ({ component: Component, redirectTo, isAuth, path, ...props }) => {
isAuth = false;
if(!isAuth) {
return <Navigate to={redirectTo} />;
}
return <Route path={path} element={<Component />} />
};
export default PrivateRoute;
update:
orginal auth was:in privateroute:
isAuth = isAuth;
one example of non-working code that would show what i want:
<Route path="/login" element={}>
<PrivateRoute isAuth={true} path="/" component={GameComponent} redirectTo='/login'>
rest of routes exist in gamecomponent..
</PrivateRoute>
If you only want GameComponent to load if use is authenticated, you will need to change your App component like this:
function App() {
return (
<Router>
<Routes>
<Route path="/login" element={<LoginComponent />} />
<PrivateRoute isAuth={true} path="/" component={GameComponent} redirectTo='/login'/>
</Routes>
</Router>
);
}
Here we are essentially putting a switch so that we can navigate to /login when there is no authentication. <Routes> is vital here, because it will only render the component that matches the exact path.
With the official release of React Router V6, the other answer is no longer valid. It will throw an error since <PrivateRoute /> isn't a <Route />.
The proper way to do it is to refactor your <PrivateRoute /> component like so...
import { Navigate, useLocation } from "react-router-dom"
const PrivateRoute = (props: { children: React.ReactNode }): JSX.Element => {
const { children } = props
// Up to you specific app to decide how to get this value
const isLoggedIn: boolean = localStorage.getItem('logged_user') !== null;
const location = useLocation()
return isLoggedIn ? (
<>{children}</>
) : (
<Navigate
replace={true}
to="/login"
state={{ from: `${location.pathname}${location.search}` }}
/>
)
}
Then whichever file you setup your routes in, you would do...
<Routes>
<Route path="/PRIVATE" element={<PrivateRoute> <PrivatePage /> </PrivateRoute>}/>
<Route path="/profile" element={<PrivateRoute> <ProfilePage /> </PrivateRoute>}/>
<Route path="/login" element={<LoginPage />}/>
<Route path="/" element={<HomePage />}/>
</Routes>
This is the proper way of doing it in V6 since only a <Route /> can be nested in a <Routes />. Then your authenticated logic gets moved into the element prop.
As an added bonus, the state={{ from: `${location.pathname}${location.search}` }} in PrivateRoute allows you to get the URL of the page they tried to enter, but was denied. This is passed to your login page, where you can redirect them back to the URL after they authenticate.
Solution for newer version
Create custom middleware <AuthGuard> and use as wrapper
<PrivateRoute> not vaild for newer version as child of <Routes>
Error "A <Route> is only ever to be used as the child of <Routes> element"
App.js
import React from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import AuthGuard from "./Routes/AuthGuard";
function App() {
return (
<div className='App'>
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/contact' element={<Contact />} />
<Route path='/guest-page' element={<AuthGuard isGuest={true}><h1>Guest Page</h1></AuthGuard>} />
<Route path='/protected-page' element={<AuthGuard requireToken={true}><h1>ProtectedPage</h1></AuthGuard>} />
</Routes>
</Router>
</div>
);
}
export default App;
AuthGuard.js
import { Route, useNavigate } from "react-router-dom";
import { useLayoutEffect } from "react";
const ProtectedRoute = ({requireToken, guest, children, ...rest}) => {
console.log(requireToken, guest, children, rest);
const navigate = useNavigate();
const hasToken = false;
let hasAccess = (!requireToken || (requireToken && hasToken));
let navigateTo = '/?login-rquired=true';
if(guest) {
hasAccess = !hasToken;
navigateTo = '/?guest=true';
console.log("Guest > hasAccess: " + hasAccess)
}
if(requireToken){
console.log("requireToken", requireToken)
}
useLayoutEffect(()=>{
if (!hasAccess) {
console.log("Not allowed");
navigate(navigateTo);
}
},[])
return (
<>{ hasAccess ? children : "Login required" }</>
)
}
export default ProtectedRoute;
I am trying to use onEnter with my reactjs app. I want to check if the session is true then make sure the user gets redirected to the dashboard page.
I am wondering if anyone knows why this would not be working?
import React, { Fragment } from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
//import Header from './core/header';
//PAGES
import Homes from './core/pages/home';
import Dashboard from './core/pages/dashboard';
import Addnew from './core/pages/addnew';
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
export function onEnter(nextState, transition, callback) {
console.log("test")
const { pathname } = nextState.location
const isLoggedIn = sessionStorage.getItem('loggedin') === true
if (pathname === '/' && isLoggedIn) {
transition('/dashboard') //redirect to Home component
}
return callback() // go as it is.
}
function App() {
return (
<div className="App">
<Router>
<Fragment>
<Route path="/" exact component={Homes} onEnter={onEnter} />
<Route path="/dashboard" exact component={Dashboard} />
<Route path="/dashboard/radio/:stationid" exact component={Dashboard} />
<Route path="/addnew" exact component={Addnew} />
</Fragment>
</Router>
</div>
);
}
export default App
First, you need to also import Redirect from react-router-dom
import { BrowserRouter as Router, Route, Redirect } from "react-router-dom";
Not exactly sure about your logic but you need to call Redirect like the following;
<Redirect
to={{
pathname: "/dashboard",
state: { from: location }
}}
/>
refer to the react-router doc https://reacttraining.com/react-router/web/example/auth-workflow
From react-router-v4 onEnter, onUpdate, and onLeave is removed,
Docs :https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md#on-properties
Try something like
<Route exact path="/home" render={() => (
isLoggedIn() ? (
<Redirect to="/first"/>
) : (
<Home />
)
)}/>
I have a Router File, where my Routes are nested under Index Component.
However, I want some other component, login which I don't want to Nest under any component but I want to use it to redirect to '/' home route.
If I use Div tags, then it is messing with my Template.
I am adding the Login component - route inside the Switch Tag.
If I don't do that I get React can only have one child error.
Does anyone know how to do a Nested Route and a Non-Nested One? Please Help.
This is my Router File.
import React, { Component } from 'react';
import './App.css';
import { Provider } from 'react-redux';
import store from './store/store';
import { Router, Route , Switch } from 'react-router-dom';
import Index from './actions/indexToggle/indexActions';
import FirstDashboard from './_layouts/views/firstDashboard';
import SecondDashboard from './_layouts/views/secondDashboard';
import ThirdDashboard from './_layouts/views/thirdDashboard';
import FourthDashboard from './_layouts/views/fourthDashboard';
import history from './history';
import FifthDashboard from './_layouts/views/fifthDashboard';
import Login from './_layouts/views/Login/login';
const Main = () => (
<Provider store={store}>
<Router history={history}>
<Switch>
<Index>
<Route exact path='/overview1' component={FirstDashboard} />
<Route exact path='/overview2' render={(props) => <SecondDashboard {...props} show="show" /> } />
<Route exact path='/overview3' component={ThirdDashboard} />
<Route exact path='/overview4' component={FourthDashboard} />
<Route exact path='/overview5' component={FifthDashboard} />
</Index>
<Route path='/login' component={Login} />
</Switch>
</Router>
</Provider>
)
export default Main;
Here what I've done. See DEMO.
I don't wanna be too confused because of this, so I choose a simple way.
routes.js
import Home from "./pages/Home";
import ComplexPath from "./pages/ComplexPath";
import Login from "./pages/Login";
export default [
{
path: "/",
component: Home,
withHeaderSidenav: true
},
{
path: "/yet/another/complex/path",
component: ComplexPath,
withHeaderSidenav: true
},
{
path: "/login",
component: Login,
withHeaderSidenav: false
}
];
Then, simply map the routes.
App.js
import React from "react";
import { Switch, Route } from "react-router-dom";
import BaseLayout from "./BaseLayout";
import routes from "./routes";
export default class extends React.Component {
state = {
withHeaderSidenav: true
};
showHeaderSidenav = (withHeaderSidenav = true) => {
this.setState({ withHeaderSidenav });
};
render() {
return (
<BaseLayout withHeaderSidenav={this.state.withHeaderSidenav}>
<Switch>
{routes.map(route => (
<Route
exact
key={route.path}
path={route.path}
render={() => (
<route.component
showHeaderSidenav={() =>
this.showHeaderSidenav(route.withHeaderSidenav)
}
/>
)}
/>
))}
</Switch>
</BaseLayout>
);
}
}
There will be a HOC for each page to handle layout changing. See pages/withBase.js in demo project.