Handling protected routes with React Router

Today, we'll look at how to handle protected routes (also known as private routes) using React Router and the Context API.

Table of contents

What are protected routes?

Protected routes prevent unauthenticated and unauthorised users from accessing certain parts of our application.

Please be aware that this is not a way to secure your application (more on this next).

A note about security

It is important to note that implementing protected routes is not a way to secure your application, but a way to control what the client sees when they try to access certain routes. Requests to your back-end should be secured with a form of authentication.

As our routing is handled on the client side, it is possible for somebody to spoof the authentication state we are about to implement and access the protected route. This is why interactions between your client and server must be secured.

Let's say somebody manages to access our dashboard without actually being authenticated. This isn't an issue as none of the requests to get any sensitive data will succeed as they will lack the authentication required.

Implementation

Context

We are going to use the Context API to provide our authentication state to the relevant components.

Let's take a look at what that might look like:

1import React, { useEffect, useState, createContext } from "react";
2
3const AuthenticationContext = createContext();
4
5const AuthenticationProvider = ({ children }) => {
6 const [authenticating, setAuthenticating] = useState(true);
7 const [user, setUser] = useState(null);
8
9 useEffect(() => {
10 // Authenticate the user and set state...
11 }, []);
12
13 return <AuthenticationContext.Provider value={{ authenticating, user }}>{children}</AuthenticationContext.Provider>;
14};
15
16export { AuthenticationContext, AuthenticationProvider };

The actual logic has been omitted from this example as it will likely differ to your implementation. A common pattern is to have an effect which runs and makes a request to your back-end which will return whether the user is authenticated or not.

For the purposes of this tutorial, let's mock this and simply immediately authenticate ourselves and set our authenticating state to false.

1useEffect(() => {
2 setUser({ id: 0, name: "Louis Young" });
3
4 setAuthenticating(false);
5}, []);

Great. Now we can consume this state in the relevant components.

Protected route component

Now, we want to build a component which allows us to interface with it similarly to how we do with the Route component from React Router.

Let's see what that looks like:

1import React, { useContext } from "react";
2
3import { Redirect, Route } from "react-router-dom";
4
5import { AuthenticationContext } from "../../context/AuthenticationContext";
6
7const ProtectedRoute = ({ component: Component, path, exact }) => {
8 const { authenticating, user } = useContext(AuthenticationContext);
9
10 if (authenticating) {
11 return <p>Authenticating...</p>;
12 }
13
14 return user ? <Route path={path} exact={exact} component={Component} /> : <Redirect to="/" />;
15};
16
17export default ProtectedRoute;

Now, let's step through this code:

  • We import our components and context
  • We define a ProtectedRoute component which accepts similar props to the standard Route component
  • We consume our context, and if we are authenticating (loading) then we show a loading state
  • We then conditionally render our route, or a redirect depending on state from our context provider

Note: You can spread any additional props on to the Route component should you need to.

This is a very simple implementation, but it works. Let's see how we'd use our new component.

Usage

We can use our new ProtectedRoute component in the same way we would use the Route component from React Router.

Here is an example of this:

1<Route path="/" component={Home} exact />
2<ProtectedRoute path="/dashboard" component={Dashboard} />

You can see that both route components offer a similar API, so if you're familiar with the standard Route component then there's no learning curve.

Example

Here's an example application showing all of the above sections glued together.

Note: This examples uses a setTimeout of 1 second to mock an API request.

Conclusion

This is a minimal example for the purposes of this tutorial, but this can be easily extended to meet your requirements.

Hopefully this article helped you to handle protected routes in your React applications and reminded you of good client/server security practices.