Protect a route with a RouteGuard component
Tags: react, javascript
Published: 2022-11-30 14:03:03


Protecting some part of your application is a common daily requirement in almost every modern software.

We are going to create a component, that is a wrapper around the Route from of react-router-dom, it will protect from unauthorized access.

### Create your basic routes
First of all bootstrap your application with any tool you prefer, I would suggest using create-react-app for a rapid spin-up or use [codesandbox.io](http://codesandbox.io) which is one of the best online IDE.

Create a folder components in your project root, then create a couple of basic components that will match later our routes in our main entry.

```jsx
// ./components/Home.js
import React from "react";
import { Link } from "react-router-dom";

const Home = () => <Link to="/protected">Go to protected area</Link>;
export default Home;
```

```jsx
// ./components/Login.js
import React from "react";

const Login = () => <h2>Please login</h2>;
export default Login;
```

Create another component in your components folder, name it Protected. This is the component that will be protected from unauthorized access.

```jsx
// ./components/Protected.js
import React from "react";

const Protected = () => <h1>Hello, I am the protected component </h1>;
export default Protected;
```

Now go in your src/index.js and create some basic routes.

```jsx
import React from "react";
import { render } from "react-dom";
import { Route, BrowserRouter as Router, Switch } from "react-router-dom";

// basic components
import Home from "./components/Home";
import Login from "./components/Login";

render(
  <Router>
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>
      <Route exact path="/user/login">
        <Login />
      </Route>
    </Switch>
  </Router>,
  document.getElementById("root")
);
```
Now if you start your app you can see your basic component by visiting your base URL "/" and "user/login" URL.

### Create a mock service / function
Now, move in your root folder, create a folder called services and export a function that simulates our UserAccess control. I am going to create a class, but you can create anything you want. Control your app implementation, for now, we'll create a random boolean value to simulate the result of the application's logic. This is not mandatory it's just for the purpose of this tutorial.

```jsx
class UserService {
// passing a force param that will be returned after the timeout
  checkAccess = async (force) => {
   // check / revalidate / refresh a token, execute a request... everything you want
    return new Promise((resolve) => {
     // create a random boolean value to simulate the result of your logic
      const rand = !Math.round(Math.random());
      setTimeout(() => {
        resolve(force ? force : rand);
      }, 1500);
    });
  };
}

export default new UserService();
```

### Create the RouteGuard component
From now you can follow the code below.

```jsx
import React, { useState, useEffect, useCallback } from "react";
import { Route, Redirect } from "react-router-dom";
import UserService from "../services/UserService";
/**
 * @name RouteGuard
 *
 * @description
 * Protect a component from access.
 * It's used at Route level.
 *
 *
 * @prop {React|Component} component The component to show in case of validation success.
 * @prop {string|function} redirect URL for redirect or a function to execute.
 * @prop {boolean} showLoader Show or not a loader.
 * @prop {string} dataCy Cypress test id
 * @prop {string} query query string to append when redirect
 *
 */
const RouteGuard = ({
  dataCy,
  component: Component,
  redirectTo,
  showLoader = true,
  query = "",
  ...rest
}) => {
  const [authCheck, setAuth] = useState(false);
  const [tokenValidationFinished, setTokenValidationFinished] = useState(false);

  // Your access control logic here, I am using a fake service.
  const verifyUser = async () => {
    const logged = await UserService.checkAccess(); // force it passing a param .checkAccess(true)
    if (logged) setAuth(true);
    setTokenValidationFinished(true);
  };

  useEffect(() => {
    verifyUser();
  }, []);

  const RedirectCheck = useCallback(
    ({ redirectTo }) => {
      if (redirectTo && typeof redirectTo === "function") {
        // send the result of auth check
        redirectTo(authCheck);
        return null;
      } else {
        return (
          <Redirect
            push
            to={{
              pathname: redirectTo,
              search: query
            }}
          />
        );
      }
    },
    [authCheck, query]
  );

  if (!tokenValidationFinished)
    return showLoader ? <span>loading...</span> : null;

  return (
    <Route
      {...rest}
      data-cy={dataCy}
      render={(props) =>
        authCheck ? (
          <Component {...props} />
        ) : (
          <RedirectCheck redirectTo={redirectTo} />
        )
      }
    />
  );
};

export default RouteGuard;
```

### Update your routes
Add your RouteGuard component, create a route protected or whatever your want. When the user will try to reach this URL the internal hook will check the access.

```jsx
import React from "react";
import { render } from "react-dom";
import { Route, BrowserRouter as Router, Switch } from "react-router-dom";

// basic components
import RouteGuard from "./components/RouteGuard";
import Home from "./components/Home";
import Login from "./components/Login";

// component to protect
import Protected from "./components/Protected";

render(
  <Router>
    <Switch>
      <RouteGuard
        exact
        path={"/protected"}
        component={Protected}
        
        // with URL and query string
        redirectTo={"user/login"}
        query="?param=1&param=2"

        // with a callback function
        /* redirectTo={(authResult) =>
          console.log(authResult ? "ok nice" : "Sorry, who are you?")
        } */
      />
      <Route exact path="/">
        <Home />
      </Route>
      <Route exact path="/user/login">
        <Login />
      </Route>
    </Switch>
  </Router>,
  document.getElementById("root")

```

Example
Click the link below to show a demo.

Example on [codesandbox](https://codesandbox.io/s/routeguard-component-pptmg)