How to Implement Authenticated Routes in React Router 4

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 Routes 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>

Edit correct-way-to-redirect-user-to-different-routes-based-on-authentication-in-reac

* 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

  1. The ProtectedRoute component's initial authorized state masks the confirmed unauthenticated state, and since the component doesn't wait for authentication confirmation it happily and incorrectly redirects to "/".
  2. The ProtectedRoute component incorrectly issues a navigation action as an unintentional side-effect via the navigate function and doesn't return valid JSX in the unauthenticated case. Use the Navigate component instead.
  3. If the user is authorized the ProtectedRoute should render the Outlet 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



Leave a reply



Submit