DEV Community

Cover image for Protect a route with a RouteGuard component
Antonio Caputo
Antonio Caputo

Posted on

Protect a route with a RouteGuard component

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

// ./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; 
Enter fullscreen mode Exit fullscreen mode
// ./components/Login.js import React from "react"; const Login = () => <h2>Please login</h2>; export default Login; 
Enter fullscreen mode Exit fullscreen mode

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

// ./components/Protected.js import React from "react"; const Protected = () => <h1>Hello, I am the protected component </h1>; export default Protected; 
Enter fullscreen mode Exit fullscreen mode

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

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") ); 
Enter fullscreen mode Exit fullscreen mode

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.

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(); 
Enter fullscreen mode Exit fullscreen mode

Create the RouteGuard component

From now you can follow the code below.

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; 
Enter fullscreen mode Exit fullscreen mode

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.

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") 
Enter fullscreen mode Exit fullscreen mode

Example
Click the link below to show a demo.

Example on codesandbox

Top comments (0)