Implementing Protected Routes In React JS

Implementing Protected Routes In React JS

When building a React application, securing certain routes is a common requirement. Whether it's a user dashboard, settings page, or any other private content, protecting routes ensures that only authenticated users can access them. In this tutorial, we'll explore how to implement protected routes in a React application.

Prerequisites

Before we begin, make sure you have a React project set up. You can use Create React App or any other method of your choice.

Setting Up Authentication Context

Let's start by setting up our authentication context. This context will provide authentication-related data and methods to its children components.Creating ProtectedRoute Component


import React, { createContext, useContext, useState } from "react";

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [token, setToken] = useState(null);

  const login = (userToken) => {
    setToken(userToken);
  };

  const logout = () => {
    setToken(null);
  };

  const isAuthenticated = !!token;

  return (
    <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within an AuthProvider");
  }

  return context;
};

Creating Protected Routes

Now, let's create a component for handling protected routes. The PrivateRoutes component checks if the user is authenticated using the useAuth hook and either renders the protected content or redirects to the login page.

import { Outlet, Navigate } from "react-router-dom";
import { useAuth } from "./AuthProvider";

const PrivateRoutes = () => {
  const { isAuthenticated } = useAuth();

  return isAuthenticated ? <Outlet /> : <Navigate to="/login" />;
};

export default PrivateRoutes;

Home Component

The Home component represents a protected route. It can only be accessed by authenticated users. Upon successful authentication, the user is redirected to this component.

import React from "react";
import { Box, Flex, Heading, Button } from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../utils/AuthProvider";

const Home = () => {
  const navigate = useNavigate();
  const { logout } = useAuth();

  const handleSignOut = () => {
    console.log("User signed out");
    logout();
    navigate("/login");
  };

  return (
    <Flex minH={"100vh"} align={"center"} justify={"center"} direction="column">
      <Box
        borderWidth={1}
        px={4}
        width="md"
        borderRadius={8}
        boxShadow="lg"
        p={8}
      >
        <Box textAlign="center">
          <Heading>Welcome to the Home Page!</Heading>
        </Box>
        <Box my={4} textAlign="left">
          <p>This is a simple home page for your application.</p>
          <Button
            width="full"
            mt={4}
            colorScheme="teal"
            onClick={handleSignOut}
          >
            Sign Out
          </Button>
        </Box>
      </Box>
    </Flex>
  );
};

export default Home;

Login Component

The LoginForm component handles user login. Upon successful login, it utilizes the login method from the authentication context, and the user is redirected to the protected home route.

import React, { useState } from "react";
import {
  Box,
  Flex,
  FormControl,
  FormLabel,
  Input,
  Button,
  Heading,
  FormErrorMessage,
} from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../utils/AuthProvider";

const LoginForm = () => {
  const { login } = useAuth();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [emailError, setEmailError] = useState("");
  const [passwordError, setPasswordError] = useState("");

  const navigate = useNavigate();

  const validateEmail = () => {
    if (!email) {
      setEmailError("Email is required");
      return false;
    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      setEmailError("Invalid email format");
      return false;
    } else {
      setEmailError("");
      return true;
    }
  };

  const validatePassword = () => {
    if (!password) {
      setPasswordError("Password is required");
      return false;
    } else {
      setPasswordError("");
      return true;
    }
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    if (validateEmail() && validatePassword()) {
      console.log("Login successful");
      login("dummyToken");
      navigate("/home");
    }
  };

  return (
    <Flex minH={"100vh"} align={"center"} justify={"center"}>
      <Box
        borderWidth={1}
        px={4}
        width="md"
        borderRadius={8}
        boxShadow="lg"
        p={8}
      >
        <Box textAlign="center">
          <Heading>Login</Heading>
        </Box>
        <Box my={4} textAlign="left">
          <form onSubmit={handleSubmit}>
            <FormControl isInvalid={!!emailError}>
              <FormLabel>Email</FormLabel>
              <Input
                type="email"
                placeholder="john.doe@example.com"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                onBlur={validateEmail}
              />
              <FormErrorMessage>{emailError}</FormErrorMessage>
            </FormControl>
            <FormControl mt={6} isInvalid={!!passwordError}>
              <FormLabel>Password</FormLabel>
              <Input
                type="password"
                placeholder="********"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                onBlur={validatePassword}
              />
              <FormErrorMessage>{passwordError}</FormErrorMessage>
            </FormControl>
            <Button
              width="full"
              mt={4}
              colorScheme="teal"
              type="submit"
              onClick={handleSubmit}
            >
              Sign In
            </Button>
          </form>
        </Box>
      </Box>
    </Flex>
  );
};

export default LoginForm;

Integrating Protected Routes in App

Finally, in your App.jsx, you integrate the protected routes within the Routes component.

// App.js
import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import LoginForm from "./components/LoginForm";
import Home from "./components/Home";
import PrivateRoutes from "./utils/PrivateRoutes";
import { AuthProvider } from "./utils/AuthProvider";
const App = () => {
  return (
    <Router>
      <AuthProvider>
        {" "}
        <Routes>
          <Route element={<PrivateRoutes />}>
            {" "}
            <Route path="/home" element={<Home />} />
          </Route>
          <Route path="/" element={<LoginForm />} />
          <Route path="/login" element={<LoginForm />} />
        </Routes>
      </AuthProvider>
    </Router>
  );
};

export default App;

Conclusion

By following these steps, you've successfully implemented protected routes in your React application. This approach ensures that certain parts of your application are accessible only to authenticated users.

Feel free to customize the authentication system and components based on your project requirements. This tutorial provides a basic structure that you can extend for more complex applications.

Github Repo: https://github.com/MuleCraft/protected-routes/tree/feature/protected-routes

Mulecraft Footer