How to implement authenticated routes in React Router 4?
You're going to want to use the Redirect
component. There's a few different approaches to this problem. Here's one I like, have a PrivateRoute component that takes in an authed
prop and then renders based on that props.
function PrivateRoute ({component: Component, authed, ...rest}) {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
Now your Route
s can look something like this
<Route path='/' exact component={Home} />
<Route path='/login' component={Login} />
<Route path='/register' component={Register} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />
If you're still confused, I wrote this post that may help -
Protected routes and authentication with React Router v4
Performing Authentication on Routes with react-router-v4
In react-router v4
, you can make use of render prop
to Route
along with the lifecycle methods to replace the onEnter
functionality existing in react-router v3.
See this answer for more details:
onEnter prop in react-router v4
However since all you want to do is authentication in the onEnter prop, you could easily create a HOC that does that
const RequireAuth = (Component) => {
return class App extends Component {
componentWillMount() {
const getToken = localStorage.getItem('token');
if(!getToken) {
this.props.history.replace({pathname: '/'});
}
}
render() {
return <Component {...this.props} />
}
}
}
export { RequireAuth }
and use it like
<Route path={"/Dashboard"} component={RequireAuth(Dashboard)}/>
Edit: In case you need to make a network call to find if the use if authenticated of not, you would write the HOC like
const RequireAuth = (Component) => {
return class App extends Component {
state = {
isAuthenticated: false,
isLoading: true
}
componentDidMount() {
AuthCall().then(() => {
this.setState({isAuthenticated: true, isLoading: false});
}).catch(() => {
this.setState({isLoading: false});
})
}
render() {
const { isAuthenticated, isLoading } = this.state;
if(isLoading) {
return <div>Loading...</div>
}
if(!isAuthenticated) {
return <Redirect to="/login" />
}
return <Component {...this.props} />
}
}
}
export { RequireAuth }
Update:
In addition to the HOC, you can also go for the PrivateRoute
component like
const PrivateRoute = ({component: Component, isAuthenticated, isLoading, ...rest }) => {
if(isLoading) {
return <div>Loading...</div>
}
if(!isAuthenticated) {
return <Redirect to="/login" />
}
return <Component {...this.props} />
}
}
}
export { PrivateRoute };
and you can use it like
class App extends Component {
state = {
isAuthenticated: false,
isLoading: true
}
componentDidMount() {
AuthCall().then(() => {
this.setState({isAuthenticated: true, isLoading: false});
}).catch(() => {
this.setState({isLoading: false});
})
}
render() {
<Router>
<div>
<Route exact path={"/"} component={Home} />
<Route path={"/SignUp"} component={SignUp} />
<Route path={"/SignIn"} component={SignIn} />
<PrivateRoute path={"/Dashboard"} component={Dashboard} isAuthenticated={this.state.isAuthenticated} isLoading={this.isLoading}/>
</div>
</Router>
}
}
React, implementing protected route with authentication with React Router Dom
Change PotectedRoute.js
to the code below, as what you are doing is more like what we used to do for React Router Dom v5
while you are using v6
.
import { Navigate, useLocation} from "react-router-dom";
export const ProtectedRoute = ({children}) => {
let location = useLocation();
if(!localStorage.getItem("loginEmail")){
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
};
The information from useLocation
passed as prop to Navigate
can be used in Login
so you send the user to that specific url where they were going to instead of a hard coded one (admin in your case), useful if you had multiple protected routes. Though it's not a requirement, you can remove it.
To know more about authentication in React Router Dom v6
, visit this example on StackBlitz from their documentation. Don't look at the editor lint errors, it's the most complete and straightforward authentication example.
How to authenticate with Routes in React.js
export default function PrivateRoute({component: Component, ...props}) {
let [PAGE_LOADING, SET_PAGE_LOADING] = useState(LOADING_TYPE.Loading);
let [AUTHENTICATION, SET_AUTHENTICATE] = useState(0);
useEffect(() => {
Auth().then((resolve) => {
SET_AUTHENTICATE(resolve);
}).catch((reject) => SET_AUTHENTICATE(reject))
.then(() => SET_PAGE_LOADING(LOADING_TYPE.Default))
}, [])
return (
PAGE_LOADING === LOADING_TYPE.Loading && AUTHENTICATION == 0 ?
<CtLabel/> :
<Route
{...props}
render={props => (
AUTHENTICATION == 10 ?
<Component {...props}/> :
<Redirect to={ROUTE_PATH.LOGIN}/>
)}/>
)
}
And for Used:
export default function BasePage() {
return (
<BrowserRouter>
<Switch>
{/*Other Routes*/}
<Private path={***} component={Home} exact/>
<Private path={***} component={Profile} exact/>
{/*Other Routes*/}
<Route component={NotFound}/>
</Switch>
</BrowserRouter>
);
}
Correct way to redirect user to different routes based on authentication in ReactJS
A more advisable method would be to decouple the auth checks from the components and abstract this into custom route components.
PrivateRoute - if the user is authenticated then a regular Route
is rendered and the props are passed through, otherwise redirect to the "/login" path for user to authenticate.
const PrivateRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Route {...props} /> : <Redirect to="/login" />;
};
AnonymousRoute - Basically the inverse of the private route. If the user is already authenticated then redirect them to the "/home" path, otherwise render a route and pass the route props through.
const AnonymousRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Redirect to="/home" /> : <Route {...props} />;
};
From here you render the Login
and Home
components into their respective custom route components.
<Switch>
<PrivateRoute
isLoggedIn={this.state.isLoggedIn} // *
path="/home"
component={Home}
/>
<AnonymousRoute
isLoggedIn={this.state.isLoggedIn} // *
path={["/login", "/"]}
component={Login}
/>
</Switch>
* NOTE: The isLoggedIn={this.state.isLoggedIn}
prop is only required here since the isLoggedIn
state resides in the App
component. A typical React application would store the auth state in a React Context or in global state like Redux, and thus wouldn't need to be explicitly passed via props, it could be accessed from within the custom route component.
Full sandbox code:
const PrivateRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Route {...props} /> : <Redirect to="/login" />;
};
const AnonymousRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Redirect to="/home" /> : <Route {...props} />;
};
class Login extends Component {
render() {
return (
<>
<h1>Login here</h1>
<button type="button" onClick={this.props.login}>
Log in
</button>
</>
);
}
}
class Home extends Component {
render() {
return (
<>
<h1>Home</h1>
<button type="button" onClick={this.props.logout}>
Log out
</button>
</>
);
}
}
export default class App extends Component {
state = {
isLoggedIn: false
};
componentDidMount() {
if (localStorage.getItem("isLoggedIn")) {
this.setState({
isLoggedIn: true
});
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.isLoggedIn !== this.state.isLoggedIn) {
localStorage.setItem("isLoggedIn", JSON.stringify(this.state.isLoggedIn));
}
}
logInHandler = () => this.setState({ isLoggedIn: true });
logOutHandler = () => this.setState({ isLoggedIn: false });
render() {
return (
<div className="App">
<div>Authenticated: {this.state.isLoggedIn ? "Yes" : "No"}</div>
<ul>
<li>
<Link to="/">/</Link>
</li>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/login">log in</Link>
</li>
</ul>
<Switch>
<PrivateRoute
isLoggedIn={this.state.isLoggedIn}
path="/home"
render={() => <Home logout={this.logOutHandler} />}
/>
<AnonymousRoute
isLoggedIn={this.state.isLoggedIn}
path={["/login", "/"]}
render={() => <Login login={this.logInHandler} />}
/>
</Switch>
</div>
);
}
}
How to use Protected Routes with react-router-dom V6 and typescript?
It's fairly trivial to create a "PrivateRoute" component, even in TypeScript.
In your case you can't directly render a Route
component as the error points out. This was a breaking change between RRDv5 and RRDv6. You can render the children
prop since you are directly wrapping the components you want to protect.
Example:
const PrivateWrapper = ({ children }: { children: JSX.Element }) => {
const auth = useAuth();
return auth?.user ? children : <Navigate to="/" replace />;
};
Usage:
<Routes>
...
<Route
path="/consoleAndReserve"
element={(
<PrivateWrapper>
<Navbar />
<ConsultReserve />
</PrivateWrapper>
)}
/>
...
</Routes>
My Private React Route (Router v6) Not Redirecting To My Login Page
probably best to return the Navigate component rather than the useNavigate hook:
import {
Navigate,
BrowserRouter,
Routes,
Route,
} from 'react-router-dom';
//My Private Route
const PrivateRoute = ({ children }) => {
const auth = JSON.parse(localStorage.getItem('token'));
return auth?.user ? children : <Navigate to="/login" />;
};
How to prevent a route from registering before jwt verifies react
Issue
- The
ProtectedRoute
component's initialauthorized
state masks the confirmed unauthenticated state, and since the component doesn't wait for authentication confirmation it happily and incorrectly redirects to"/"
. - The
ProtectedRoute
component incorrectly issues a navigation action as an unintentional side-effect via thenavigate
function and doesn't return valid JSX in the unauthenticated case. Use theNavigate
component instead. - If the user is authorized the
ProtectedRoute
should render theOutlet
for a protected route to be rendered into, and only redirect to login if unauthorized.
Solution
The ProtectedRoute
component should use an indeterminant initial authorized
state that doesn't match either the authenticated or unauthenticated state, and wait for the auth status to be confirmed before rendering either the Outlet
or Navigate
components.
Example:
import { useState, useContext } from "react";
import { useLocation, Navigate, Outlet } from "react-router-dom";
import AuthContext from "../Context/AuthProvider";
const ProtectedRoute = ({ access }) => {
const location = useLocation();
const [authorized, setAuthorized] = useState(); // initially undefined!
const { auth } = useContext(AuthContext);
useEffect(() => {
const authorize = async () => {
try {
await access(auth.accessToken);
setAuthorized(true);
} catch (err) {
setAuthorized(false);
}
};
authorize();
}, []);
if (authorized === undefined) {
return null; // or loading indicator/spinner/etc
}
return authorized
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />;
};
Move the login route outside the ProtectedRoute
layout route.
<Routes>
<Route
path="/login"
element={<Login login={login} access={access} />}
/>
<Route
path="/signup"
element={<Signup signup={signup} access={access} />}
/>
<Route
path="/forgot-password"
element={<ForgotPassword access={access} />}
/>
<Route
path="/reset-password"
element={<ResetPassword access={access} />}
/>
... other unprotected routes ...
<Route element={<ProtectedRoute access={access} />}>
... other protected routes ...
</Route>
</Routes>
To protect the login/signup/forgot/reset/etc routes
Create an AnonymousRoute
component that inverts the Outlet
and Navigate
components on the authentication status. This time authenticated users get redirected off the route.
const AnonymousRoute = ({ access }) => {
const [authorized, setAuthorized] = useState(); // initially undefined!
const { auth } = useContext(AuthContext);
useEffect(() => {
const authorize = async () => {
try {
await access(auth.accessToken);
setAuthorized(true);
} catch (err) {
setAuthorized(false);
}
};
authorize();
}, []);
if (authorized === undefined) {
return null; // or loading indicator/spinner/etc
}
return authorized
? <Navigate to="/" replace />
: <Outlet />;
};
...
<Routes>
<Route element={<AnonymousRoute access={access} />}>
<Route path="/login" element={<Login login={login} access={access} />} />
<Route path="/signup" element={<Signup signup={signup} access={access} />} />
<Route path="/forgot-password" element={<ForgotPassword access={access} />} />
<Route path="/reset-password" element={<ResetPassword access={access} />} />
... other protected anonymous routes ...
</Route>
... unprotected routes ...
<Route element={<ProtectedRoute access={access} />}>
... other protected authenticated routes ...
</Route>
</Routes>
Related Topics
Implementing Pagination in Mongodb
Unexpected Token Colon JSON After Jquery.Ajax#Get
Remove Property for All Objects in Array
How to Get the Mouse Position Without Events (Without Moving the Mouse)
When and Where Does JavaScript Run, How About PHP? How to Combine the Two
JavaScript Onclick Event Over Flash Object
Chrome Autofill/Autocomplete No Value for Password
How to Ensure a <Select> Form Field Is Submitted When It Is Disabled
Performance Differences Between Visibility:Hidden and Display:None
Jquery Slider, How to Make "Step" Size Change
Why Is My Infinite Loop Blocking When It Is in an Async Function
Es6 Variable Import Name in Node.Js
JavaScript "This" Pointer Within Nested Function
How to Hide Form Code from View Code/Inspect Element Browser
Add/Delete Table Rows Dynamically Using JavaScript