Authentication is one of the most basic features in any web app, in this post we will see some of the best practices on how to implement authentication in react apps.
There are several ways of protecting your routes from an unauthenticated user in your React Application, for example, it can be done by using HOC pattern or by using React Context API. Lets see first how to protect routes using React Router.
Protecting Routes using React Router
We will make a React component which we will use for routing to protected pages. In this component, we will be using Route and Redirect component from React Router. See the code below for the implementation of this component which we named PrivateRoute.
import React from "react";
import { Route, Redirect } from "react-router-dom";
const PrivateRoute = ({ component: Component, ...rest }) => {
return(
<Route
{...rest}
render={props =>
condition
? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
)
}
/>
);}
export default PrivateRoute;
As you can see PrivateRoute is using Redirect and Route component with a render method which is a prop of Route component (this is also a pattern in React known as Render Props). Quoting from React Router documentation
Rendering a <Redirect> will navigate to a new location. The new location will override the current location in the history stack.
and
The Route component is perhaps the most important in React Router to understand and to learn to use well. It’s most basic responsibility is to render some UI when a location matches the route’s path.
So Route component will render a Component(which we want to be protected) or Redirect Component depending on the condition which we have placed in render method of Route.
Now ‘condition’ is the place where will we put our logic for user authentication. If the user is authenticated it will be able to access the Component, else it will be redirected to login page(or any other page).
Consider a scenario in which we have an auth state which we are managing in our redux state having a boolean value which equals to true if a user is authenticated else false for such a case we can do something like below
import React from "react";
import { Route, Redirect } from "react-router-dom";
import {connect} from 'react-redux';
const PrivateRoute = ({ component: Component, ...rest, auth }) => {
return(
<Route
{...rest}
render={props =>
auth
? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
)
}
/>
);}
const mapStateToProps = ({auth})=>({auth})
export default connect(mapStateToProps)(PrivateRoute);
Now we can use this PrivateRoute component instead of Route component for routing to protected pages. Usually, we do this when we define our routes
<Switch>
<Route path="/dashboard" component={MainPage} />
<Route path="/login" component={LoginPage} />
<Route path="/signup" component={SignUp} />
</Switch>
Now instead of Route we will use PrivateRoute for the component which we want to be accessible only to an authenticated user. Just like
...
import PrivateRoute from './PrivateRoute'
<Switch>
<PrivateRoute path="/dashboard" component={MainPage} />
<Route path="/login" component={LoginPage} />
<Route path="/signup" component={SignUp} />
</Switch>
Similarly, we can also make other routes component based on the user roles. For example for Admin Role, Guest Role etc.
Protecting Routes using Higher Order Components
In this section, we will see how to protect our routes using Higher Order Components.
But firstly, What are Higher Order Components?
Higher Order Components are a pattern that have emerged in React, it is a function that takes an component as parameter and returns an enhanced component. Below is an example of what a higher order function looks like
import React from "react";
export default function(WrappedComponent) {
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
};
}
But the HOC in above example does not enhance the component it is just returning the component as it is. We need to tweak the class component for protecting our routes and we will be using the same case as we have used in our Protecting Routes using React Router section, where we have an auth state which we are managing in our redux state having a boolean value which equals to true if a user is authenticated else false.
So we will modify our code to the below
import React from "react";
import { connect } from "react-redux";
export default function(WrappedComponent) {
class Authentication extends React.Component {
componentDidMount() {
this.checkAuthentication();
}
componentDidUpdate() {
this.checkAuthentication();
}
checkAuthentication = () => {
if (!this.props.auth) {
this.props.history.push("/");
}
};
render() {
return <WrappedComponent {...this.props} />;
}
}
const mapStateToProps = ({ auth }) => ({ auth });
return connect(mapStateToProps)(Authentication);
}
checkAuthentication method redirects the user to login page if user is not authenticated. We are calling this function in componentDidMount and componentDidUpdate lifecycle.
componentDidMount is called only on initial mount and will check the auth state initially and componentDidUpdate is called when there is an update caused by changes to props, so if there is any change in our auth state, componentDidUpdate will be called.
Now we can use this ‘requireAuth’ HOC to protect our pages. Just need to pass our component(which need to be authenticated) to ‘requireAuth’ HOC and then pass it to component prop of Route in our route file
...
import requireAuth from './requireAuth'
<Switch>
<Route path="/dashboard" component={requireAuth(MainPage)} />
<Route path="/login" component={LoginPage} />
<Route path="/signup" component={SignUp} />
</Switch>
This will ensure that our component will be only accessible to authenticated user.
References : https://reacttraining.com/react-router/web/example/auth-workflow