BackendMay 5, 20259 min read

Firebase Authentication in React

Adding auth from scratch is a week of work. Firebase Auth is an afternoon. Here's how to implement Google Sign-In, email/password, protected routes, and persistent sessions the right way.

Firebase Authentication in React

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

  1. 1Create a project at console.firebase.google.com.
  2. 2Go to Authentication โ†’ Sign-in methods and enable Email/Password and Google.
  3. 3Go to Project Settings โ†’ Your apps โ†’ Add Web app. Copy the config object.
  4. 4Install the SDK: `npm install firebase`
typescript
// 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

typescript
// 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

typescript
// 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

typescript
// 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.

Written by

Muhiu Din

Full-Stack Engineer

More Articles