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;
// ./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.
// ./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.
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.
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.
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.
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¶m=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
Top comments (0)