Authentication is one of those features that sounds simple - 'just add a login page' - until you're knee-deep in JWT refresh tokens, session management, password reset flows, and OAuth callback handling. Firebase Auth handles all of that infrastructure so you can focus on your actual product. Here's how to implement it properly.
# Setup: Firebase Project and SDK
- 1Create a project at console.firebase.google.com.
- 2Go to Authentication โ Sign-in methods and enable Email/Password and Google.
- 3Go to Project Settings โ Your apps โ Add Web app. Copy the config object.
- 4Install the SDK: `npm install firebase`
// lib/firebase.ts - initialize once, import everywhere
import { initializeApp, getApps } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
// Prevent re-initialization in Next.js hot reload
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
export const auth = getAuth(app);# Auth Context: The Right Way to Share User State
// context/AuthContext.tsx
"use client";
import { createContext, useContext, useEffect, useState } from "react";
import { onAuthStateChanged, User } from "firebase/auth";
import { auth } from "@/lib/firebase";
interface AuthContextType {
user: User | null;
loading: boolean;
}
const AuthContext = createContext<AuthContextType>({ user: null, loading: true });
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Firebase calls this whenever auth state changes (login, logout, token refresh)
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
setLoading(false);
});
return unsubscribe; // cleanup listener on unmount
}, []);
return (
<AuthContext.Provider value={{ user, loading }}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => useContext(AuthContext);# Google Sign-In: 10 Lines of Code
// components/GoogleSignInButton.tsx
"use client";
import { signInWithPopup, GoogleAuthProvider, signOut } from "firebase/auth";
import { auth } from "@/lib/firebase";
import { useAuth } from "@/context/AuthContext";
const provider = new GoogleAuthProvider();
export function GoogleSignInButton() {
const { user } = useAuth();
const handleSignIn = async () => {
try {
await signInWithPopup(auth, provider);
// onAuthStateChanged in AuthContext fires automatically
} catch (error) {
console.error("Sign-in failed:", error);
}
};
if (user) {
return (
<div>
<img src={user.photoURL!} alt="avatar" className="w-8 h-8 rounded-full" />
<button onClick={() => signOut(auth)}>Sign Out</button>
</div>
);
}
return (
<button onClick={handleSignIn}>
Continue with Google
</button>
);
}# Protected Routes
// components/ProtectedRoute.tsx
"use client";
import { useAuth } from "@/context/AuthContext";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !user) {
router.push("/login"); // redirect unauthenticated users
}
}, [user, loading, router]);
if (loading) return <div>Loading...</div>;
if (!user) return null; // prevent flash of protected content
return <>{children}</>;
}
// Usage in any page:
// export default function Dashboard() {
// return <ProtectedRoute><DashboardContent /></ProtectedRoute>;
// }Firebase Auth tokens expire after one hour. Firebase automatically refreshes them in the background - you don't need to handle this manually. The `onAuthStateChanged` listener also fires on token refresh, keeping your user state current.


