Firebase Auth State Management
Auth state management answers one critical question every page of your app needs: is there a logged-in user right now? Firebase provides a real-time listener that monitors login status and updates your app automatically whenever a user logs in or out.
The Problem Without Auth State Management
Without proper auth state management, apps face a common bug: the page loads, auth.currentUser returns null (because Firebase hasn't finished restoring the session from storage yet), the app shows the login screen, then Firebase finishes restoring the session and the user is actually logged in — causing a flash of wrong content.
Think of it like checking your mailbox the moment you wake up. You walk downstairs half-asleep, open the box, and see nothing. You assume no mail came. But the delivery person hasn't arrived yet — they come 30 seconds later. You checked too early.
The onAuthStateChanged listener solves this. Instead of checking once at startup, it watches the mailbox continuously and tells you the moment mail arrives or leaves.
The onAuthStateChanged Listener
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "./firebase";
onAuthStateChanged(auth, (user) => {
if (user) {
// A user is logged in
console.log("User is signed in:", user.email);
console.log("UID:", user.uid);
showDashboard();
} else {
// No user is logged in
console.log("No user signed in");
showLoginPage();
}
});
Firebase calls this callback function every time the auth state changes:
- When the page first loads (to restore a previous session)
- When a user successfully logs in
- When a user logs out
- When a session expires or is revoked
Auth State Flow Diagram
App starts
|
v
Firebase checks local storage for saved session
|
+---> Session found --> onAuthStateChanged fires with User object
|
+---> No session ---> onAuthStateChanged fires with null
User logs in
|
v
Firebase saves session to local storage
|
v
onAuthStateChanged fires with User object
User logs out
|
v
Firebase clears session from local storage
|
v
onAuthStateChanged fires with null
Unsubscribing from the Listener
onAuthStateChanged returns an unsubscribe function. Call it when your component or page no longer needs to track auth state — for example, when a page unloads. This prevents memory leaks.
// Store the unsubscribe function
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
console.log("Signed in:", user.email);
} else {
console.log("Signed out");
}
});
// Later, when done:
unsubscribe(); // stops the listener
In React apps, call unsubscribe inside the useEffect cleanup function so the listener stops when the component unmounts.
Auth State in React
Managing auth state in React typically involves a context provider that shares the current user across all components:
import React, { createContext, useContext, useEffect, useState } from "react";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "./firebase";
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(undefined); // undefined = still loading
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser); // null if signed out, User object if signed in
});
return unsubscribe; // cleanup on unmount
}, []);
return (
<AuthContext.Provider value={{ user }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
return useContext(AuthContext);
}
Using the Auth Hook in Components
import { useAuth } from "./AuthContext";
function ProfilePage() {
const { user } = useAuth();
if (user === undefined) return <p>Loading...</p>;
if (!user) return <p>Please log in.</p>;
return <p>Welcome, {user.displayName || user.email}!</p>;
}
The three states of user are important:
undefined— Firebase hasn't checked yet (show a loading spinner)null— no user is signed in (show login page)- User object — user is signed in (show app content)
Protecting Routes
Auth state management powers route protection — preventing logged-out users from accessing private pages. Here is a simple protected route pattern:
function PrivateRoute({ children }) {
const { user } = useAuth();
if (user === undefined) return <p>Loading...</p>;
if (!user) {
// Redirect to login page
window.location.href = "/login";
return null;
}
return children;
}
// Usage:
<PrivateRoute>
<DashboardPage />
</PrivateRoute>
Persistence: Controlling How Long Sessions Last
Firebase stores auth sessions in the browser's local storage by default, so users stay logged in across page refreshes and browser restarts. You can change this behavior:
import { browserLocalPersistence, browserSessionPersistence,
inMemoryPersistence, setPersistence } from "firebase/auth";
import { auth } from "./firebase";
// Options:
// browserLocalPersistence — stays logged in until explicitly signed out
// browserSessionPersistence — logged out when browser tab closes
// inMemoryPersistence — logged out on page refresh
await setPersistence(auth, browserSessionPersistence);
// Now sign in...
Use browserSessionPersistence for apps handling sensitive data like banking, where longer sessions pose a security risk.
Key Takeaway
Always use onAuthStateChanged instead of checking auth.currentUser directly at startup. The listener fires with the correct user state once Firebase finishes restoring the session. Store the returned unsubscribe function and call it when you no longer need the listener. In React, wrap your app in an auth context provider so every component can access the current user without prop drilling.
