Introduction to Firebase Authentication
Firebase Authentication is a fully managed identity service provided by Google that handles user sign-up, sign-in, and account management for your applications. It supports multiple authentication methods including email/password, phone number, and federated identity providers like Google, Facebook, GitHub, Apple, Twitter, and Microsoft. Rather than building your own auth system from scratch — which involves handling password hashing, session management, token generation, and security vulnerabilities — Firebase Auth gives you a production-ready solution backed by Google's infrastructure.
Why Firebase Authentication Matters
Authentication is the gateway to your application. A poorly implemented auth system can lead to data breaches, account takeovers, and a terrible user experience. Firebase Auth solves several critical problems:
- Security by default — Firebase handles password hashing (using scrypt), token signing, and session management securely out of the box
- Multiple sign-in methods — Add email/password, social providers, phone auth, or anonymous auth with minimal code changes
- Scalability — Firebase Auth scales automatically with your user base, from zero to millions of users
- Integration with Firebase services — Auth integrates seamlessly with Firestore, Realtime Database, Cloud Functions, and Security Rules for end-to-end access control
- Cross-platform support — Unified auth experience across web, iOS, Android, Unity, and Flutter
- Free tier generosity — Firebase Auth is free for unlimited users; only phone verification (SMS) has usage-based pricing
Prerequisites and Project Setup
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Before diving into code, you need a Firebase project. Here's how to get started:
Step 1: Create a Firebase Project
Navigate to the Firebase Console, click "Add project," and follow the prompts. You can accept the default project ID or customize it. Once created, you'll land on the project dashboard.
Step 2: Register Your Web App
From the project overview, click the web icon (</>) to register a web app. Give it a nickname (e.g., "MyApp Web"). Firebase will generate configuration credentials. You can optionally set up Firebase Hosting at this stage, but it's not required for authentication.
Step 3: Install the Firebase SDK
Depending on your build system, you have several options. Here are the most common approaches:
Option A: npm/yarn (recommended for modern web apps)
npm install firebase
# or
yarn add firebase
Option B: CDN via Module Scripts
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
import { getAuth } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js";
</script>
Option C: CDN with Global Namespace (legacy)
<script src="https://www.gstatic.com/firebasejs/10.7.1/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.7.1/firebase-auth-compat.js"></script>
For the rest of this guide, we'll use the modular npm approach, which is the recommended pattern for modern JavaScript applications.
Firebase Auth Configuration in the Firebase Console
Before writing code, you must enable the authentication methods you want to use. This is done entirely in the Firebase Console — no code changes needed to toggle providers.
Enabling Email/Password Sign-In
In the Firebase Console, navigate to Authentication → Sign-in method. Click on Email/Password, toggle the "Enable" switch, and click Save. That's it — email/password authentication is now active for your project.
Configuring Social Identity Providers
For social providers like Google, Facebook, or GitHub, you need OAuth credentials from the respective developer platform. Here's how to set up the most common ones:
Google Sign-In Configuration
Google sign-in is the easiest social provider to configure because it works with your Firebase project's existing Google Cloud project:
- In Firebase Console → Authentication → Sign-in method, click Google
- Toggle "Enable" and click Save
- That's it — no additional OAuth configuration is required for the default setup
- For production, you may want to configure the OAuth consent screen in the Google Cloud Console under "APIs & Services → OAuth consent screen" to customize the branding users see when signing in
GitHub Sign-In Configuration
GitHub requires you to create an OAuth App in GitHub's developer settings:
- Go to GitHub Developer Settings → OAuth Apps → New OAuth App
- Set the Authorization callback URL to the URL Firebase provides in the GitHub sign-in method panel (it looks like:
https://[PROJECT_ID].firebaseapp.com/__/auth/handler) - After registering, you'll get a Client ID and Client Secret
- Back in Firebase Console, enter the Client ID and Client Secret in the GitHub provider panel
- Toggle "Enable" and save
Facebook Sign-In Configuration
- Go to Meta for Developers → My Apps → Create App
- Select "Consumer" product type and follow the setup wizard
- Under "Products," add Facebook Login → Web
- Enter your site's URL and save
- Copy the App ID and App Secret from Settings → Basic
- In Firebase Console, paste these into the Facebook provider panel
- Copy the OAuth redirect URI from Firebase and add it to Facebook Login settings → Valid OAuth Redirect URIs
- Toggle "Enable" and save
Initializing Firebase and the Auth Service in Your Project
With the Firebase SDK installed and providers enabled in the console, you're ready to initialize Firebase in your application code. The recommended practice is to create a dedicated configuration file:
Create a Firebase Configuration File
// src/firebase/config.js
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore"; // optional, for database rules integration
const firebaseConfig = {
apiKey: "AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
authDomain: "your-project-id.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project-id.appspot.com",
messagingSenderId: "123456789012",
appId: "1:123456789012:web:abc123def456ghi789",
};
// Initialize Firebase App
const app = initializeApp(firebaseConfig);
// Initialize Auth service
export const auth = getAuth(app);
// Optional: Initialize other services
export const db = getFirestore(app);
// Default export for convenience
export default app;
This single configuration file becomes the central hub for all Firebase services in your application. Every component that needs auth imports from here, ensuring consistent initialization across your codebase.
Understanding the Firebase Config Values
- apiKey — This is NOT a secret. It's a public identifier used to route requests to your Firebase project. It's safe to expose in client-side code because Firebase enforces security through rules, not API key secrecy
- authDomain — The domain used for authentication redirects and popups (OAuth flows)
- projectId — Your unique project identifier
- storageBucket — Default Cloud Storage bucket (useful if you store user avatars)
- messagingSenderId — Used for Firebase Cloud Messaging (optional for auth)
- appId — Unique identifier for your web app within the project
Implementing Email/Password Authentication
Email/password authentication is the most fundamental auth method. Let's implement the full lifecycle: registration, login, email verification, and password reset.
User Registration (Sign Up)
// src/auth/register.js
import { auth } from "../firebase/config";
import { createUserWithEmailAndPassword, sendEmailVerification } from "firebase/auth";
async function registerUser(email, password) {
try {
// Step 1: Create the user account
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
// Step 2: The user is now signed in automatically
const user = userCredential.user;
console.log("User registered:", user.uid);
// Step 3: Send email verification (best practice)
await sendEmailVerification(user);
console.log("Verification email sent to:", user.email);
return { success: true, user };
} catch (error) {
// Handle specific error codes
switch (error.code) {
case "auth/email-already-in-use":
console.error("This email address is already registered");
break;
case "auth/invalid-email":
console.error("The email address is invalid");
break;
case "auth/weak-password":
console.error("Password must be at least 6 characters");
break;
case "auth/operation-not-allowed":
console.error("Email/password sign-up is not enabled in Firebase Console");
break;
default:
console.error("Registration error:", error.message);
}
return { success: false, error: error.code, message: error.message };
}
}
// Usage example
registerUser("user@example.com", "securePassword123")
.then((result) => {
if (result.success) {
// Redirect to dashboard or show success message
}
});
User Login (Sign In)
// src/auth/login.js
import { auth } from "../firebase/config";
import { signInWithEmailAndPassword } from "firebase/auth";
async function loginUser(email, password) {
try {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
// Check if email is verified (optional but recommended)
if (!user.emailVerified) {
console.warn("Please verify your email address before proceeding");
// You might want to re-send verification or restrict access here
}
console.log("User logged in successfully:", user.uid);
return { success: true, user };
} catch (error) {
switch (error.code) {
case "auth/invalid-email":
console.error("Invalid email address format");
break;
case "auth/user-not-found":
console.error("No account found with this email address");
break;
case "auth/wrong-password":
console.error("Incorrect password");
break;
case "auth/invalid-credential":
console.error("Invalid credentials — check both email and password");
break;
case "auth/too-many-requests":
console.error("Account temporarily locked due to many failed attempts. Try again later or reset your password");
break;
default:
console.error("Login error:", error.message);
}
return { success: false, error: error.code, message: error.message };
}
}
// Usage example with a simple form handler
document.getElementById("login-form").addEventListener("submit", async (e) => {
e.preventDefault();
const email = document.getElementById("email").value;
const password = document.getElementById("password").value;
const result = await loginUser(email, password);
if (result.success) {
window.location.href = "/dashboard";
} else {
// Display error to user
document.getElementById("error-message").textContent = result.message;
}
});
Password Reset Flow
// src/auth/resetPassword.js
import { auth } from "../firebase/config";
import { sendPasswordResetEmail } from "firebase/auth";
async function resetPassword(email) {
try {
await sendPasswordResetEmail(auth, email);
console.log("Password reset email sent successfully");
// Firebase sends an email with a link that opens the Firebase reset UI
// The link expires after a short period for security
return { success: true };
} catch (error) {
switch (error.code) {
case "auth/user-not-found":
// For security, you might still show a success message
// to avoid revealing which emails are registered
console.log("If this email is registered, a reset link has been sent");
break;
case "auth/invalid-email":
console.error("Please provide a valid email address");
break;
default:
console.error("Password reset error:", error.message);
}
return { success: false, error: error.code, message: error.message };
}
}
Customizing the Password Reset Email Template
In the Firebase Console under Authentication → Templates, you can customize the email templates for verification, password reset, and email change notifications. You can modify the subject line, message body, and even the reply-to address. For the password reset email, the key variable is %LINK% which Firebase replaces with the actual reset link. A well-crafted template improves user trust and reduces support tickets:
Subject: Reset your password for ${APP_NAME}
Hello,
We received a request to reset the password for your account.
Click the link below to set a new password:
%LINK%
If you did not request a password reset, you can safely ignore this email.
This link will expire in 24 hours.
Best regards,
The ${APP_NAME} Team
Social Authentication Implementation
Social authentication eliminates the friction of creating yet another username/password combination. Firebase handles the entire OAuth flow and returns a unified user object regardless of the provider used.
Google Sign-In
// src/auth/googleSignIn.js
import { auth } from "../firebase/config";
import { GoogleAuthProvider, signInWithPopup, signInWithRedirect } from "firebase/auth";
// Create a Google provider instance
const googleProvider = new GoogleAuthProvider();
// Optional: Configure OAuth scopes
googleProvider.addScope("profile");
googleProvider.addScope("email");
// For additional Google APIs, add more scopes:
// googleProvider.addScope("https://www.googleapis.com/auth/calendar.readonly");
// Optional: Custom parameters
googleProvider.setCustomParameters({
prompt: "select_account", // Always show account selector
// login_hint: "user@example.com", // Pre-fill email for known users
});
// Popup sign-in (best for web, non-mobile)
async function signInWithGooglePopup() {
try {
const result = await signInWithPopup(auth, googleProvider);
// This gives you a Google Access Token for additional API calls
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
// The signed-in user info
const user = result.user;
console.log("Google sign-in successful:", user.displayName);
console.log("User email:", user.email);
console.log("Profile picture:", user.photoURL);
return { success: true, user, token };
} catch (error) {
switch (error.code) {
case "auth/popup-closed-by-user":
console.log("User closed the popup before completing sign-in");
break;
case "auth/cancelled-popup-request":
console.log("Multiple popup requests detected — only one can be open at a time");
break;
case "auth/popup-blocked":
console.error("Popup was blocked by the browser — try redirect sign-in instead");
break;
case "auth/account-exists-with-different-credential":
console.error("An account already exists with this email using a different sign-in method");
// Handle account linking here — see account linking section below
break;
default:
console.error("Google sign-in error:", error.message);
}
return { success: false, error: error.code, message: error.message };
}
}
// Redirect sign-in (better for mobile browsers or where popups fail)
async function signInWithGoogleRedirect() {
try {
await signInWithRedirect(auth, googleProvider);
// The page will redirect to Google, then back to your app
// Use getRedirectResult() on the return page to complete sign-in
} catch (error) {
console.error("Google redirect sign-in error:", error.message);
}
}
// Handle redirect result — call this on page load after redirect
async function handleRedirectResult() {
try {
const result = await getRedirectResult(auth);
if (result) {
// User successfully signed in after redirect
const user = result.user;
console.log("Redirect sign-in complete:", user.displayName);
return { success: true, user };
}
return { success: false, reason: "no_result" };
} catch (error) {
console.error("Redirect result error:", error.message);
return { success: false, error: error.code };
}
}
GitHub Sign-In
// src/auth/githubSignIn.js
import { auth } from "../firebase/config";
import { GithubAuthProvider, signInWithPopup } from "firebase/auth";
const githubProvider = new GithubAuthProvider();
// Optional: Request additional scopes
githubProvider.addScope("repo"); // Access to repositories
githubProvider.addScope("user:email"); // Access to user's email addresses
async function signInWithGitHub() {
try {
const result = await signInWithPopup(auth, githubProvider);
const credential = GithubAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
const user = result.user;
// GitHub may provide multiple emails — the primary one is in user.email
// Additional emails are available via the token if you added the user:email scope
console.log("GitHub sign-in successful:", user.displayName);
console.log("GitHub username:", user.providerData[0]?.displayName);
return { success: true, user, token };
} catch (error) {
if (error.code === "auth/account-exists-with-different-credential") {
// Handle account linking
console.error("Account exists with different credential — needs linking");
}
console.error("GitHub sign-in error:", error.message);
return { success: false, error: error.code, message: error.message };
}
}
Handling Account Linking When Multiple Providers Share an Email
A common edge case: a user signs in with Google, but their email is already registered via email/password. Firebase throws auth/account-exists-with-different-credential. Here's how to gracefully handle this:
// src/auth/accountLinking.js
import { auth } from "../firebase/config";
import {
GoogleAuthProvider,
signInWithPopup,
signInWithEmailAndPassword,
linkWithCredential,
} from "firebase/auth";
async function handleGoogleSignInWithLinking() {
const googleProvider = new GoogleAuthProvider();
try {
// Attempt Google sign-in
const result = await signInWithPopup(auth, googleProvider);
return { success: true, user: result.user };
} catch (error) {
if (error.code === "auth/account-exists-with-different-credential") {
// Firebase provides the pending credential in the error
const pendingCredential = GoogleAuthProvider.credentialFromError(error);
// Ask the user to sign in with their existing email/password first
const email = error.customData?.email;
if (email && pendingCredential) {
// Prompt user for their existing password
const password = prompt(
`An account already exists for ${email}. ` +
`Please enter your existing password to link your Google account.`
);
if (!password) {
return { success: false, reason: "user_cancelled" };
}
try {
// Sign in with email/password first
const emailSignInResult = await signInWithEmailAndPassword(
auth,
email,
password
);
// Now link the Google credential to the existing account
await linkWithCredential(
emailSignInResult.user,
pendingCredential
);
console.log("Accounts linked successfully!");
return { success: true, user: emailSignInResult.user };
} catch (linkError) {
console.error("Linking failed:", linkError.message);
return { success: false, error: linkError.code };
}
}
}
return { success: false, error: error.code };
}
}
Managing Auth State and User Sessions
Understanding auth state management is critical. Firebase Auth persists sessions across page reloads using browser storage (IndexedDB or localStorage), but you need to set up a listener to react to state changes.
The onAuthStateChanged Listener
This is the single most important function in Firebase Auth. It fires whenever the user's sign-in state changes — initial page load, sign-in, sign-out, token refresh. You should set this up once, early in your application's lifecycle:
// src/auth/authState.js
import { auth } from "../firebase/config";
import { onAuthStateChanged } from "firebase/auth";
// This runs on every auth state change
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in
console.log("User is signed in:", user.uid);
console.log("Email:", user.email);
console.log("Email verified:", user.emailVerified);
console.log("Display name:", user.displayName);
// You can now:
// - Update UI to show authenticated state
// - Fetch user-specific data from Firestore
// - Initialize user-specific features
// - Redirect from public-only pages (like login page)
// Get the user's ID token for backend verification
user.getIdToken().then((idToken) => {
console.log("ID Token (send to your backend for verification):", idToken);
});
} else {
// User is signed out
console.log("User is signed out");
// You can now:
// - Update UI to show unauthenticated state
// - Clear user-specific cached data
// - Redirect to login page
// - Reset application state
}
});
// You can also get the current user synchronously (may be null on first load)
const currentUser = auth.currentUser;
if (currentUser) {
console.log("Current user (synchronous):", currentUser.uid);
} else {
console.log("No user signed in currently");
}
Building an Auth Guard / Protected Route Pattern
A common pattern in web apps is protecting certain routes. Here's a simple implementation using the auth state observer:
// src/auth/authGuard.js
import { auth } from "../firebase/config";
import { onAuthStateChanged } from "firebase/auth";
function protectPage() {
return new Promise((resolve) => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
unsubscribe(); // Clean up listener immediately after first check
if (user) {
resolve({ allowed: true, user });
} else {
// Redirect to login page
window.location.href = "/login?redirect=" + encodeURIComponent(window.location.pathname);
resolve({ allowed: false });
}
});
});
}
// Usage: Call this at the top of any page that requires authentication
protectPage().then((result) => {
if (result.allowed) {
// Initialize the protected page content
console.log("Welcome back,", result.user.displayName);
document.getElementById("app").style.display = "block";
}
});
Auth State Persistence Configuration
By default, Firebase Auth persists the session across browser tabs and restarts using IndexedDB. You can customize this behavior:
// src/auth/persistence.js
import { auth } from "../firebase/config";
import {
browserLocalPersistence,
browserSessionPersistence,
inMemoryPersistence,
setPersistence,
} from "firebase/auth";
// Option 1: Local persistence (default) — survives browser restarts
async function enableLocalPersistence() {
await setPersistence(auth, browserLocalPersistence);
console.log("Auth state will persist across browser restarts and tabs");
}
// Option 2: Session persistence — only survives in current tab/session
async function enableSessionPersistence() {
await setPersistence(auth, browserSessionPersistence);
console.log("Auth state will be cleared when the browser session ends");
// Useful for sensitive applications where you want auto-logout on tab close
}
// Option 3: In-memory persistence — no persistence at all
async function enableMemoryOnlyPersistence() {
await setPersistence(auth, inMemoryPersistence);
console.log("Auth state exists only in memory — no persistence");
// Useful for testing or when you manage sessions yourself
}
// Call ONE of these before any sign-in attempt
// Typically you'd call this in your app initialization
enableLocalPersistence(); // This is the default behavior
User Profile Management
Once authenticated, users can update their profile information. Firebase provides methods for updating display names, profile photos, and email addresses:
// src/auth/profileManagement.js
import { auth } from "../firebase/config";
import {
updateProfile,
updateEmail,
sendEmailVerification,
updatePassword,
reauthenticateWithCredential,
EmailAuthProvider,
} from "firebase/auth";
// Update display name and photo URL
async function updateUserProfile(displayName, photoURL) {
const user = auth.currentUser;
if (!user) {
console.error("No user signed in");
return { success: false };
}
try {
await updateProfile(user, {
displayName: displayName || user.displayName,
photoURL: photoURL || user.photoURL,
});
console.log("Profile updated successfully");
return { success: true };
} catch (error) {
console.error("Profile update error:", error.message);
return { success: false, error: error.code };
}
}
// Update email address (requires recent authentication)
async function changeUserEmail(newEmail, currentPassword) {
const user = auth.currentUser;
if (!user) return { success: false };
try {
// Re-authenticate first (required for sensitive operations)
const credential = EmailAuthProvider.credential(user.email, currentPassword);
await reauthenticateWithCredential(user, credential);
// Now update the email
await updateEmail(user, newEmail);
// Send verification to the new email
await sendEmailVerification(user);
console.log("Email updated — verification sent to new address");
return { success: true };
} catch (error) {
console.error("Email change error:", error.message);
return { success: false, error: error.code };
}
}
// Change password (also requires recent authentication)
async function changeUserPassword(currentPassword, newPassword) {
const user = auth.currentUser;
if (!user) return { success: false };
try {
const credential = EmailAuthProvider.credential(user.email, currentPassword);
await reauthenticateWithCredential(user, credential);
await updatePassword(user, newPassword);
console.log("Password changed successfully");
return { success: true };
} catch (error) {
console.error("Password change error:", error.message);
return { success: false, error: error.code };
}
}
User Sign-Out Implementation
Signing out is straightforward but should be paired with proper cleanup:
// src/auth/signOut.js
import { auth } from "../firebase/config";
import { signOut } from "firebase/auth";
async function logoutUser() {
try {
// Optional: Clear any app-specific cached data first
// localStorage.removeItem("userPreferences");
// sessionStorage.clear();
await signOut(auth);
console.log("User signed out successfully");
// The onAuthStateChanged listener will fire and handle UI updates
// You can also redirect manually:
window.location.href = "/login";
return { success: true };
} catch (error) {
console.error("Sign-out error:", error.message);
return { success: false, error: error.code };
}
}
// For a complete cleanup on logout
async function fullLogout() {
// Clear any Firebase offline-persisted data if using Firestore
// await clearPersistence(db);
// Sign out
await signOut(auth);
// Clear local storage (be selective — keep non-auth data if needed)
const keysToRemove = ["userData", "authToken", "userPreferences"];
keysToRemove.forEach((key) => localStorage.removeItem(key));
// Redirect to public page
window.location.href = "/";
}
Advanced Configuration: Custom Claims and Admin Control
Custom claims allow you to attach role-based permissions directly to a user's auth token. These claims are included in the JWT and can be read by Security Rules and your backend:
Setting Custom Claims (Server-Side / Admin SDK)
// This code runs in a Cloud Function or your backend server
// NOT in the client-side code
const admin = require("firebase-admin");
admin.initializeApp();
async function setAdminClaim(userId) {
try {
await admin.auth().setCustomUserClaims(userId, {
admin: true,
role: "super_admin",
accessLevel: 5,
});
console.log("Custom claims set for user:", userId);
return { success: true };
} catch (error) {
console.error("Error setting custom claims:", error.message);
return { success: false, error: error.message };
}
}
// Verify custom claims in Firestore Security Rules:
/*
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /admin/{document=**} {
allow read, write: if request.auth != null && request.auth.token.admin == true;
}
}
}
*/
Reading Custom Claims on the Client
// src/auth/customClaims.js
import { auth } from "../firebase/config";
async function checkUserRole() {
const user = auth.currentUser;
if (!user) return null;
// Force token refresh to get latest claims
// Custom claims require a fresh token — they don't update instantly
await user.getIdToken(true); // true = force refresh
const idTokenResult = await user.getIdTokenResult();
const claims = idTokenResult.claims;
console.log("User claims:", claims);
console.log("Is admin:", claims.admin);
console.log("Role:", claims.role);
return claims;
}
Security Configuration and Best Practices
Security is not optional — it's the foundation of any authentication system. Here are the critical security configurations you must implement:
1. Restrict API Keys in the Google Cloud Console
By default, your Firebase API key is unrestricted. Go to the Google Cloud Console → APIs & Services → Credentials, find your API key, and add Application Restrictions:
- Set it to HTTP Referrers for web apps
- Add your production domains (e.g.,
https://yourapp.com/*) - Add your local development domains (e.g.,
http://localhost:3000/*) - Remove the unrestricted setting — this prevents attackers from using your API key on their own domains
2. Configure Firebase Security Rules
Authentication is only half the story — authorization (access control