Skip to main content
RapidDev - Software Development Agency
firebase-tutorial

How to Use Firebase Auth with React

To use Firebase Auth with React, create an AuthContext provider that wraps onAuthStateChanged in a useEffect hook and shares the current user state across your component tree. Use signInWithEmailAndPassword and createUserWithEmailAndPassword for email/password flows, signInWithPopup for OAuth providers like Google, and signOut to end sessions. Protect routes by checking the user state in a wrapper component that redirects unauthenticated users.

What you'll learn

  • How to build an AuthContext provider with onAuthStateChanged for global auth state
  • How to implement sign-up, sign-in, and sign-out with email/password
  • How to add Google Sign-In with signInWithPopup
  • How to create a ProtectedRoute component for route guarding
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate9 min read15-20 minReact 18+, Firebase JS SDK v9+, all Firebase plansMarch 2026RapidDev Engineering Team
TL;DR

To use Firebase Auth with React, create an AuthContext provider that wraps onAuthStateChanged in a useEffect hook and shares the current user state across your component tree. Use signInWithEmailAndPassword and createUserWithEmailAndPassword for email/password flows, signInWithPopup for OAuth providers like Google, and signOut to end sessions. Protect routes by checking the user state in a wrapper component that redirects unauthenticated users.

Setting Up Firebase Authentication in React

Firebase Auth integrates cleanly with React through the Context API and hooks pattern. This tutorial shows you how to create an AuthContext that listens to auth state changes, implement email/password and Google sign-in flows, handle errors with user-friendly messages, and protect routes so only authenticated users can access certain pages. The pattern works with any React router (React Router, TanStack Router) and scales from simple apps to complex role-based systems.

Prerequisites

  • A React 18+ project (Create React App, Vite, or Next.js)
  • Firebase JS SDK v9+ installed (npm install firebase)
  • A Firebase project with Email/Password sign-in enabled
  • React Router v6+ for route protection examples

Step-by-step guide

1

Initialize Firebase and export the auth instance

Create a firebase configuration file that initializes the Firebase app and exports the auth instance. This file is imported throughout your app whenever you need to call auth methods. Store config values in environment variables for security.

typescript
1// src/lib/firebase.ts
2import { initializeApp } from 'firebase/app';
3import { getAuth } from 'firebase/auth';
4
5const firebaseConfig = {
6 apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
7 authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
8 projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
9 storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
10 messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
11 appId: import.meta.env.VITE_FIREBASE_APP_ID,
12};
13
14const app = initializeApp(firebaseConfig);
15export const auth = getAuth(app);
16export default app;

Expected result: Firebase is initialized and the auth instance is available for import throughout the application.

2

Create an AuthContext provider with useAuth hook

The AuthContext wraps onAuthStateChanged in a useEffect and provides the current user, loading state, and auth methods to the entire component tree. The useAuth custom hook gives any component quick access to the auth state. Place the AuthProvider at the root of your app so all components can consume it.

typescript
1// src/contexts/AuthContext.tsx
2import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
3import {
4 onAuthStateChanged,
5 signInWithEmailAndPassword,
6 createUserWithEmailAndPassword,
7 signInWithPopup,
8 GoogleAuthProvider,
9 signOut as firebaseSignOut,
10 User,
11} from 'firebase/auth';
12import { auth } from '../lib/firebase';
13
14interface AuthContextType {
15 user: User | null;
16 loading: boolean;
17 signIn: (email: string, password: string) => Promise<void>;
18 signUp: (email: string, password: string) => Promise<void>;
19 signInWithGoogle: () => Promise<void>;
20 signOut: () => Promise<void>;
21}
22
23const AuthContext = createContext<AuthContextType | null>(null);
24
25export function AuthProvider({ children }: { children: ReactNode }) {
26 const [user, setUser] = useState<User | null>(null);
27 const [loading, setLoading] = useState(true);
28
29 useEffect(() => {
30 const unsubscribe = onAuthStateChanged(auth, (user) => {
31 setUser(user);
32 setLoading(false);
33 });
34 return unsubscribe;
35 }, []);
36
37 const value: AuthContextType = {
38 user,
39 loading,
40 signIn: async (email, password) => {
41 await signInWithEmailAndPassword(auth, email, password);
42 },
43 signUp: async (email, password) => {
44 await createUserWithEmailAndPassword(auth, email, password);
45 },
46 signInWithGoogle: async () => {
47 await signInWithPopup(auth, new GoogleAuthProvider());
48 },
49 signOut: async () => {
50 await firebaseSignOut(auth);
51 },
52 };
53
54 return (
55 <AuthContext.Provider value={value}>
56 {children}
57 </AuthContext.Provider>
58 );
59}
60
61export function useAuth() {
62 const context = useContext(AuthContext);
63 if (!context) throw new Error('useAuth must be used within AuthProvider');
64 return context;
65}

Expected result: Any component can call useAuth() to access the current user, loading state, and auth methods.

3

Build a login form with error handling

Create a login component that uses the useAuth hook to sign in. Handle Firebase Auth errors by catching the error code and displaying a user-friendly message. Common errors include user-not-found, wrong-password, and invalid-email.

typescript
1// src/components/LoginForm.tsx
2import { useState, FormEvent } from 'react';
3import { useAuth } from '../contexts/AuthContext';
4import { FirebaseError } from 'firebase/app';
5
6export function LoginForm() {
7 const { signIn, signInWithGoogle } = useAuth();
8 const [email, setEmail] = useState('');
9 const [password, setPassword] = useState('');
10 const [error, setError] = useState('');
11 const [loading, setLoading] = useState(false);
12
13 async function handleSubmit(e: FormEvent) {
14 e.preventDefault();
15 setError('');
16 setLoading(true);
17
18 try {
19 await signIn(email, password);
20 } catch (err) {
21 if (err instanceof FirebaseError) {
22 switch (err.code) {
23 case 'auth/user-not-found':
24 setError('No account found with this email.');
25 break;
26 case 'auth/wrong-password':
27 setError('Incorrect password.');
28 break;
29 case 'auth/invalid-email':
30 setError('Please enter a valid email.');
31 break;
32 default:
33 setError('Sign-in failed. Please try again.');
34 }
35 }
36 } finally {
37 setLoading(false);
38 }
39 }
40
41 return (
42 <form onSubmit={handleSubmit}>
43 {error && <p style={{ color: 'red' }}>{error}</p>}
44 <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" required />
45 <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" required />
46 <button type="submit" disabled={loading}>Sign In</button>
47 <button type="button" onClick={signInWithGoogle}>Sign in with Google</button>
48 </form>
49 );
50}

Expected result: Users can sign in with email/password or Google, with clear error messages for failed attempts.

4

Create a ProtectedRoute component

A ProtectedRoute component checks if the user is authenticated before rendering protected content. If the user is not signed in, redirect them to the login page. If auth state is still loading, show a loading indicator to prevent a flash of the login page before the auth check completes.

typescript
1// src/components/ProtectedRoute.tsx
2import { Navigate } from 'react-router-dom';
3import { useAuth } from '../contexts/AuthContext';
4
5export function ProtectedRoute({ children }: { children: React.ReactNode }) {
6 const { user, loading } = useAuth();
7
8 if (loading) {
9 return <div>Loading...</div>;
10 }
11
12 if (!user) {
13 return <Navigate to="/login" replace />;
14 }
15
16 return <>{children}</>;
17}
18
19// Usage in your router:
20// <Route path="/dashboard" element={
21// <ProtectedRoute><Dashboard /></ProtectedRoute>
22// } />

Expected result: Protected routes redirect unauthenticated users to login and show content only after authentication is confirmed.

5

Wire everything together in the app root

Wrap your app with the AuthProvider at the root level so all components and routes can access auth state. Set up your router with public routes (login, sign-up) and protected routes (dashboard, settings) using the ProtectedRoute component.

typescript
1// src/App.tsx
2import { BrowserRouter, Routes, Route } from 'react-router-dom';
3import { AuthProvider } from './contexts/AuthContext';
4import { ProtectedRoute } from './components/ProtectedRoute';
5import { LoginForm } from './components/LoginForm';
6import { Dashboard } from './pages/Dashboard';
7
8export default function App() {
9 return (
10 <AuthProvider>
11 <BrowserRouter>
12 <Routes>
13 <Route path="/login" element={<LoginForm />} />
14 <Route
15 path="/dashboard"
16 element={
17 <ProtectedRoute>
18 <Dashboard />
19 </ProtectedRoute>
20 }
21 />
22 </Routes>
23 </BrowserRouter>
24 </AuthProvider>
25 );
26}

Expected result: The app has complete auth routing: unauthenticated users see the login page, authenticated users access the dashboard.

Complete working example

AuthContext.tsx
1import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
2import {
3 onAuthStateChanged,
4 signInWithEmailAndPassword,
5 createUserWithEmailAndPassword,
6 signInWithPopup,
7 GoogleAuthProvider,
8 signOut as firebaseSignOut,
9 User,
10 AuthError,
11} from 'firebase/auth';
12import { auth } from '../lib/firebase';
13
14interface AuthContextType {
15 user: User | null;
16 loading: boolean;
17 signIn: (email: string, password: string) => Promise<void>;
18 signUp: (email: string, password: string) => Promise<void>;
19 signInWithGoogle: () => Promise<void>;
20 signOut: () => Promise<void>;
21}
22
23const AuthContext = createContext<AuthContextType | null>(null);
24
25export function AuthProvider({ children }: { children: ReactNode }) {
26 const [user, setUser] = useState<User | null>(null);
27 const [loading, setLoading] = useState(true);
28
29 useEffect(() => {
30 const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
31 setUser(currentUser);
32 setLoading(false);
33 });
34 return unsubscribe;
35 }, []);
36
37 async function signIn(email: string, password: string) {
38 await signInWithEmailAndPassword(auth, email, password);
39 }
40
41 async function signUp(email: string, password: string) {
42 await createUserWithEmailAndPassword(auth, email, password);
43 }
44
45 async function signInWithGoogle() {
46 const provider = new GoogleAuthProvider();
47 await signInWithPopup(auth, provider);
48 }
49
50 async function signOut() {
51 await firebaseSignOut(auth);
52 }
53
54 return (
55 <AuthContext.Provider value={{
56 user, loading, signIn, signUp, signInWithGoogle, signOut,
57 }}>
58 {!loading && children}
59 </AuthContext.Provider>
60 );
61}
62
63export function useAuth(): AuthContextType {
64 const context = useContext(AuthContext);
65 if (!context) {
66 throw new Error('useAuth must be used within an AuthProvider');
67 }
68 return context;
69}
70
71export function mapAuthError(code: string): string {
72 const errorMap: Record<string, string> = {
73 'auth/user-not-found': 'No account found with this email.',
74 'auth/wrong-password': 'Incorrect password.',
75 'auth/email-already-in-use': 'An account with this email already exists.',
76 'auth/weak-password': 'Password must be at least 6 characters.',
77 'auth/invalid-email': 'Please enter a valid email address.',
78 'auth/too-many-requests': 'Too many attempts. Please try again later.',
79 'auth/popup-closed-by-user': 'Sign-in popup was closed.',
80 };
81 return errorMap[code] ?? 'An error occurred. Please try again.';
82}

Common mistakes when using Firebase Auth with React

Why it's a problem: Not returning the unsubscribe function from useEffect, causing memory leaks with multiple listeners

How to avoid: Always return the onAuthStateChanged unsubscribe function from useEffect: return unsubscribe; This stops the listener when the component unmounts.

Why it's a problem: Rendering protected content before the auth check completes, causing a flash of the login page

How to avoid: Track a loading state that starts as true and flips to false after onAuthStateChanged fires. Show a loading indicator while loading is true.

Why it's a problem: Placing AuthProvider inside BrowserRouter, causing auth state to be unavailable in route guards

How to avoid: Wrap AuthProvider outside BrowserRouter so auth context is accessible to all routing components.

Why it's a problem: Displaying raw Firebase error messages to users instead of mapping error codes to friendly messages

How to avoid: Check err.code (like 'auth/wrong-password') and map it to a human-readable message. Firebase error messages are technical and confusing for end users.

Best practices

  • Use the Context + useEffect + onAuthStateChanged pattern for global auth state management in React
  • Always provide a loading state to prevent UI flashes before auth initialization completes
  • Map Firebase Auth error codes to user-friendly messages instead of displaying raw error strings
  • Store Firebase config in environment variables, not hardcoded in source code
  • Create a ProtectedRoute wrapper component for consistent route guarding across the app
  • Place AuthProvider at the root level, outside the router, so all components have access to auth state
  • Clean up the onAuthStateChanged listener by returning the unsubscribe function from useEffect

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Build Firebase Auth for a React app with TypeScript. Show an AuthContext provider with onAuthStateChanged in useEffect, signInWithEmailAndPassword, signInWithPopup for Google, a ProtectedRoute component, error handling with error code mapping, and React Router v6 integration.

Firebase Prompt

Create a React AuthContext with Firebase Auth v9 modular SDK. Include onAuthStateChanged in useEffect, email/password signIn and signUp, Google sign-in with signInWithPopup, signOut, a useAuth hook, a ProtectedRoute component with React Router, and an error code mapper.

Frequently asked questions

Does Firebase Auth persist sessions in React by default?

Yes. Firebase Auth defaults to browserLocalPersistence, which persists the session in IndexedDB and survives browser restarts. Users stay signed in until they explicitly sign out.

Can I use Firebase Auth without React Context?

Yes. You can call Firebase Auth methods directly from any component. However, the Context pattern centralizes auth state and prevents redundant onAuthStateChanged listeners.

How do I add Google Sign-In to my React app?

Enable Google as a provider in Firebase Console, create a GoogleAuthProvider instance, and call signInWithPopup(auth, provider). No additional packages are needed for web.

What happens when the Firebase ID token expires?

The Firebase client SDK automatically refreshes the ID token before it expires (every ~55 minutes). You do not need to handle token refresh manually on the client side.

Can I use Firebase Auth with state management libraries like Redux or Zustand?

Yes. Instead of React Context, you can store the user in Redux or Zustand. Set up the onAuthStateChanged listener in a top-level component and dispatch actions or update the store on auth state changes.

How do I add role-based access control with Firebase Auth?

Use custom claims set with the Admin SDK: admin.auth().setCustomUserClaims(uid, { role: 'admin' }). Access claims on the client via user.getIdTokenResult().claims. Enforce roles in Firestore security rules with request.auth.token.role.

Can RapidDev help implement authentication in my React application?

Yes. RapidDev can build complete auth systems with email/password, social logins, role-based access control, and protected routing patterns tailored to your React application.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.