🧭 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
localStorageaccess - 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
useAuthwith 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)