Lesson 12 — Authentication Custom Hook (useAuth)

🧭 Introduction

Authentication logic is one of the most repeated and most misunderstood parts of React applications.

Common problems you’ll see in real projects:

  • Login logic scattered across components
  • Auth state mixed with UI code
  • Repeated localStorage access
  • Difficult logout and session handling

Professional React applications solve this by using a dedicated authentication hook — commonly called useAuth.

In this lesson, you’ll learn how to design a clean, reusable, and scalable authentication custom hook.


🎯 What You’ll Learn in This Lesson

By the end of this lesson, you will understand:

  • What authentication state really is
  • What logic belongs in useAuth
  • How to manage login, logout, and session
  • How to persist auth state
  • How this hook scales to real apps

🧠 Mental Model (Very Important)

Authentication = Application State, not UI state

So:

  • ❌ Auth logic should not live inside components
  • ✅ Auth logic should be centralized

This makes your app:

  • Predictable
  • Secure
  • Easy to maintain

🔑 What Does an Auth Hook Manage?

A proper useAuth hook typically manages:

✅ User data
✅ Auth token
✅ Login / logout actions
✅ Authentication status
✅ Persistent session


🧩 Designing the Auth State

Let’s start with a clear state shape:

const initialAuthState = {
  user: null,
  token: null,
  isAuthenticated: false
};

This keeps logic explicit and readable.


🧪 Building useAuth (Step-by-Step)

Step 1 — Create the Hook

import { useState, useEffect, useCallback } from "react";

function useAuth() {
  const [user, setUser] = useState(null);
  const [token, setToken] = useState(null);

  const isAuthenticated = Boolean(token);

  return {
    user,
    token,
    isAuthenticated
  };
}

This is the foundation.


🔐 Step 2 — Persist Session (localStorage)

Authentication should survive page refresh.

useEffect(() => {
  const storedAuth = localStorage.getItem("auth");
  if (storedAuth) {
    const parsed = JSON.parse(storedAuth);
    setUser(parsed.user);
    setToken(parsed.token);
  }
}, []);

Now auth state is restored on reload.


🔑 Step 3 — Login Logic

const login = useCallback((userData, authToken) => {
  setUser(userData);
  setToken(authToken);

  localStorage.setItem(
    "auth",
    JSON.stringify({ user: userData, token: authToken })
  );
}, []);

Why useCallback?

  • Stable reference
  • Safe to pass around

🚪 Step 4 — Logout Logic

const logout = useCallback(() => {
  setUser(null);
  setToken(null);
  localStorage.removeItem("auth");
}, []);

Clean, predictable logout.


🧩 Final useAuth Hook

function useAuth() {
  const [user, setUser] = useState(null);
  const [token, setToken] = useState(null);

  const isAuthenticated = Boolean(token);

  useEffect(() => {
    const stored = localStorage.getItem("auth");
    if (stored) {
      const parsed = JSON.parse(stored);
      setUser(parsed.user);
      setToken(parsed.token);
    }
  }, []);

  const login = useCallback((userData, authToken) => {
    setUser(userData);
    setToken(authToken);
    localStorage.setItem(
      "auth",
      JSON.stringify({ user: userData, token: authToken })
    );
  }, []);

  const logout = useCallback(() => {
    setUser(null);
    setToken(null);
    localStorage.removeItem("auth");
  }, []);

  return {
    user,
    token,
    isAuthenticated,
    login,
    logout
  };
}


🔍 Using useAuth in Components

function Login() {
  const { login } = useAuth();

  const handleLogin = async () => {
    const response = await apiLogin();
    login(response.user, response.token);
  };

  return <button onClick={handleLogin}>Login</button>;
}

function Navbar() {
  const { user, logout, isAuthenticated } = useAuth();

  return (
    <nav>
      {isAuthenticated ? (
        <>
          <span>{user.name}</span>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <span>Guest</span>
      )}
    </nav>
  );
}


⚠ Important Limitation (Critical Insight)

⚠️ This hook alone is NOT enough for large apps.

Why?

  • Multiple components need same auth state
  • Each call creates a new hook instance

👉 Solution:

  • Combine useAuth with Context API

(Coming in next section 😉)


🔐 Security Notes (Frontend Reality)

Important truths:

  • Tokens in localStorage can be read by JS
  • Frontend auth is about UI control, not true security
  • Backend must always validate tokens

👉 Frontend auth = experience, not protection.


🚨 Common Mistakes

❌ Mistake 1: Storing Too Much Data

Store only what you need.


❌ Mistake 2: Mixing API Calls in Hook

Keep API calls separate or injectable.


❌ Mistake 3: Assuming Auth = Security

Frontend auth ≠ backend security.


🧠 Real-World Evolution Path

Small app:

useAuth

Medium app:

useAuth + Context

Large app:

Auth Context + API hooks + token refresh logic


🎯 Best Practices

✅ Centralize auth logic
✅ Persist session safely
✅ Keep API separate
✅ Combine with Context for global state
✅ Keep auth state minimal


❓ FAQs — useAuth

🔹 Can I store token in sessionStorage?

Yes — for shorter sessions.


🔹 Should I store password?

Never.


🔹 Can useAuth handle token refresh?

Yes, but that’s an advanced topic.


🔹 Is Context mandatory?

For multi-component apps — yes.


🧠 Quick Recap

✔ Auth logic belongs in a hook
✔ useAuth centralizes login & logout
✔ localStorage persists session
✔ Combine with Context for scale
✔ Clean design prevents bugs


🎉 Conclusion

A well-designed useAuth hook is the backbone of any real-world React application.

Once you build it:

  • Auth logic becomes predictable
  • Components stay clean
  • Scaling becomes easier

This lesson completes your Custom Hooks Mastery section ⚛️🧠


👉 Next Section

SECTION 4 — Advanced State Management
Lesson 13 — Redux Toolkit (Advanced Concepts)

Leave a Comment