React Protected Routes - Role based
Published: 2023-12-07 15:41:56

## Introduction
Hi developers, in this tutorial, we will go through how to created protected routes in ReactJs. For this, I have create Node+Express+MongoDB server for user login/registration. You can use any other backend stack like PHP/Python or any other. For frontend state management I have used Zustand, you can choose any other library you prefer.

## Requirements
You should have basic understanding of ReactJS.

## Project Setup
npm create vite@latest
after above command, you will see wizard, choose react and javascript

For CSS and styling, I have used tailwindcss. I assume you know how to include tailwindcss in react projects. If not, please follow instruction on tailwindcss site.

## Dependencies
npm i react-router-dom axios prop-types zustand

## Code and Explanation

**App.jsx**
```
import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Error from "./pages/Error";
import Layout1 from "./components/Layouts/Layout1";
import ProtectedRoute from "./components/Auth/ProtectedRoute";
import Dashboard from "./pages/member-pages/Dashboard";
import Login from "./pages/Login";
import Unauthorized from "./pages/Unauthorized";
function App() {
  return (
    <>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Layout1 />}>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            <Route path="/login" element={<Login />} />
            <Route path="/unauthorized" element={<Unauthorized />} />
            <Route path="*" element={<Error />} />
            <Route element={<ProtectedRoute allowedRoles={['registered']}/>}>
              <Route path="/dashboard" element={<Dashboard/>}/>
            </Route>
          </Route>
        </Routes>
      </BrowserRouter>
    </>
  );
}

export default App;

```

**Auth/ProtectedRoute.jsx**

```
import { Navigate, Outlet, useLocation } from "react-router-dom";
import { useUserStore } from "../../store/useStore";
import PropTypes from "prop-types";
const ProtectedRoute = (props) => {
  const user = useUserStore((state) => state.user);
  const location = useLocation();
  return props.allowedRoles.includes(user?.role)
  ? <Outlet /> 
  : user ? <Navigate to="/unauthorized" state={{from: location}} replace /> 
  : <Navigate to="/login" state={{ from: location }} replace />
  
};

ProtectedRoute.propTypes = {
  allowedRoles: PropTypes.array,
};

export default ProtectedRoute;

```
**Layouts/Layout1.jsx**

```
import { Outlet } from "react-router-dom";
import Footer from "../page-parts/Footer";
import Header from "../page-parts/Header";

const Layout1 = () => {
  return (
    <>
      <div className="m-auto max-w-5xl border border-black bg-blue-950 text-white my-10 rounded-xl">
        <Header />
        <div className="p-10">
          <Outlet />
        </div>
        <Footer />
      </div>
    </>
  );
};

export default Layout1;

```
**page-parts/Header.jsx**

```
import { Link } from "react-router-dom";
import { useUserStore } from "../../store/useStore";

const Header = () => {
  const user = useUserStore((state) => state.user);
  const signOut = useUserStore((state) => state.signOut);
  return (
    <div className="bg-gray-300 text-black rounded-t-xl">
      <nav className="flex justify-between">
        <img src="https://picsum.photos/100/50.jpg" />
        <ul className="flex gap-5 p-3">
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/contact">Contact</Link>
          </li>
          <li>
            <Link to="/services">Services</Link>
          </li>

          {user ? (
            <>
              <li>
                <Link to="/dashboard">Dashboard</Link>
              </li>
              <li>
                <button onClick={() => signOut()}>Logout</button>
              </li>
            </>
          ) : (
            <li>
              <Link to="/login">Login</Link>
            </li>
          )}
        </ul>
      </nav>
    </div>
  );
};

export default Header;

```

**page-parts/Footer.jsx**

```
const Footer = () => {
  return (
    <div className="text-white w-full ">
      <div className="flex items-center justify-center">Copyright &copy; 2023</div>
    </div>
  );
};

export default Footer;

```

**pages/Home.jsx**

```
const Home = () => {
  return (
    <div className="text-justify">
      <h1 className="text-4xl">Welcome to Home Page</h1>      
    </div>
  )
}

export default Home
```
**pages/About.jsx**

```
const About = () => {
  return (
    <div>About</div>
  )
}

export default About
```

**pages/Error.jsx**

```
const Error = () => {
  return (
    <div>404 - not found</div>
  )
}

export default Error
```

**pages/Login.jsx**

```
import { useState } from "react";
import { useUserStore } from "../store/useStore";
import { useNavigate } from "react-router-dom";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const signin = useUserStore((state) => state.signIn);
  const navigate = useNavigate();
  const handleLogin = async() => {
    const status = await signin(email, password);
    if (status) {
      navigate("/dashboard");
    }
  };
  return (
    <>
      <h3 className="text-2xl text-center">Login</h3>
      <hr className="headingHr" />
      <div className="m-auto w-[400px] rounded p-3 flex flex-row justify-center bg-slate-400">
        <table className="">
          <tbody>
            <tr>
              <td>
                <label htmlFor="email">Email</label>
              </td>
              <td>
                <input
                  type="email"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                  name=""
                  id="email"
                />
              </td>
            </tr>

            <tr>
              <td>
                <label htmlFor="password">Password</label>
              </td>
              <td>
                <input
                  type="password"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                  name=""
                  id="password"
                />
              </td>
            </tr>

            <tr>
              <td></td>
              <td>
                <button onClick={() => handleLogin()} className="button">
                  Login
                </button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </>
  );
};

export default Login;

```

**pages/Unauthorized.jsx**

```
import { useUserStore } from "../store/useStore"

const Unauthorized = () => {
  const user = useUserStore(state => state.user);
  console.log(user);
  return (
    <div>You are not allowed to do this action!!!</div>
  )
}

export default Unauthorized
```

**pages/member-pages/dashboard**

```
const Dashboard = () => {
  return (
    <div>Dashboard</div>
  )
}

export default Dashboard
```

**store/axiosHTTP.js**

```
import axios from "axios";

const axiosHttp = axios.create({
  baseURL: 'http://localhost:3000'
});

export default axiosHttp;
```

**store/BaseSlice.js**

```
export const BaseSlice = (set) => ({
  name: 'Base Slice',
  loading: false,
  setLoading: (stat) => {
    set((state) => ({
      ...stat,
      loading: state
    }))
  }
});
```

**store/UserSlice.js**

```
import axiosHttp from "./axiosHttp";

export const UserSlice = (set) => ({
  name: 'User',
  user: null,
  token: null,
  setUser: async(user) => {
    set({user: user});
  },

  getUser: async(id) => {
    const json = await axiosHttp.get(`/user/${id}`);
    return await json.data;
  },
  signIn: async (email, password) => {
    try {
      //await timeout(2000);
      const json = await axiosHttp.post(`${import.meta.env.VITE_SERVER_URL}/api/v1/auth/signin`, { email, password });
      const data = await json.data;
      //console.log(data.user);
      set(() => ({
        user: data.user,
        token: data.token,
      }));
      return true;
    } catch (error) {
      return null;
    }
  },
  signUp: async (form) => {
    try {
      const json = await axiosHttp.post("/register", form);
      const data = await json.data;
      set(() => ({
        user: data.user,
        token: data.accessToken,
      }));
      return {token: data.accessToken, error: null, message: 'User registered'}
    } catch (error) {      
      const errMsg = await error.response;
      console.log(errMsg.data);
      return {message: errMsg.data, error: true, token: null};
    }
  },
  signOut: () => {
    set({
      user: null,
      token: null,
    });
  },

});
```

**store/useStore.js**

```
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware"
import { BaseSlice } from "./BaseSlice";
import { UserSlice } from "./UserSlice";
import { SongSlice } from "./SongSlice";
export const useBaseStore = create((set, get) => ({
  ...BaseSlice(set, get)
}));

export const useUserStore = create(
  persist((set, get) => ({
    ...UserSlice(set, get)
}),{name: 'userStore',storage: createJSONStorage(() => sessionStorage)}
));
```

From backend API, we need following format response at time of login.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3rsdshrtxwaudzqfrkv6.png)

Once you completed these steps, check your website.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kgttteghp07g57ubfht5.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdzmea7bvjccna4bjs5b.png)

Happy Coding.