DEV Community

Joodi
Joodi

Posted on

useAuth Hook in React

Image description

Authentication and authorization are crucial aspects of web applications. Instead of handling them manually in multiple components, we can create a reusable hook to simplify the logic. In this article, we'll first implement authentication logic inside a component and then refactor it using a custom useAuth hook.

Authentication logic without a custom hook

Let's start by creating a React component that manages user authentication manually.

import React, { useState } from "react";

const AuthComponent = () => {
  const [user, setUser] = useState(null);

  const login = (username) => {
    setUser({ name: username, role: "user" });
  };

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

  return (
    <div>
      {user ? (
        <>
          <h2>Welcome, {user.name}!</h2>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <button onClick={() => login("John Doe")}>Login</button>
      )}
    </div>
  );
};

export default AuthComponent;
Enter fullscreen mode Exit fullscreen mode

Issues with this approach

  • Authentication logic is tied to a single component, making it hard to reuse.
  • No centralized way to manage user state across the app.
  • Authorization checks need to be manually repeated in multiple components.

Creating a Custom useAuth Hook

To make authentication reusable, we'll create a useAuth hook that manages login, logout, and user state globally.

AuthProvider (Context Provider)

import { useState, createContext } from "react";

export const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(() => {
    const storedUser = localStorage.getItem("user");
    return storedUser ? JSON.parse(storedUser) : null;
  });

  const login = (username, role) => {
    const userData = { name: username, role };
    setUser(userData);
    localStorage.setItem("user", JSON.stringify(userData));
  };

  const logout = () => {
    setUser(null);
    localStorage.removeItem("user");
  };

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

useAuth Hook

import { useContext } from "react";
import { AuthContext } from "./AuthProvider";

export const useAuth = () => {
  const { user, login, logout } = useContext(AuthContext);

  const isAuthorized = (requiredRole) => {
    return user && user.role === requiredRole;
  };

  return { user, login, logout, isAuthorized };
};
Enter fullscreen mode Exit fullscreen mode

Advantages

✅ Can be used across multiple components.
✅ Provides a centralized authentication state.
✅ Eliminates redundant authentication logic.

Using useAuth in a Component

Let's refactor our authentication component to use the useAuth hook.

import React from "react";
import { useAuth } from "./useAuth";

const AuthComponent = () => {
  const { user, login, logout } = useAuth();

  return (
    <div>
      {user ? (
        <>
          <h2>Welcome, {user.name}!</h2>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <button onClick={() => login("John Doe", "user")}>Login</button>
      )}
    </div>
  );
};

export default AuthComponent;
Enter fullscreen mode Exit fullscreen mode

Wrapping the Root Component with AuthProvider

To make authentication available across the app, wrap your root component with AuthProvider.

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { AuthProvider } from "./AuthProvider";
import App from "./App";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

Example: Protecting Admin Routes

import React from "react";
import { useAuth } from "./useAuth";

const AdminPanel = () => {
  const { user, isAuthorized } = useAuth();

  if (!user || !isAuthorized("admin")) {
    return <h2>Access Denied</h2>;
  }

  return <h2>Welcome to the Admin Panel</h2>;
};

export default AdminPanel;
Enter fullscreen mode Exit fullscreen mode

Example: Protecting Routes with Higher-Order Component (HOC)

import React from "react";
import { useAuth } from "./useAuth";
import { Navigate } from "react-router-dom";

const ProtectedRoute = ({ children, requiredRole }) => {
  const { user, isAuthorized } = useAuth();

  if (!user || (requiredRole && !isAuthorized(requiredRole))) {
    return <Navigate to="/login" replace />;
  }

  return children;
};

export default ProtectedRoute;
Enter fullscreen mode Exit fullscreen mode

Advantages

✅ Simplifies role-based access control.
✅ Avoids repeating authorization checks.
✅ Easily protect routes with a reusable ProtectedRoute component.

Top comments (0)