To use Firebase Auth with Next.js, initialize the client-side Firebase SDK for sign-in flows and the Firebase Admin SDK for server-side session verification in API routes and middleware. Use onAuthStateChanged in a React context provider to manage client-side auth state, and verify ID tokens with admin.auth().verifyIdToken() on the server. Pass the ID token as a cookie or Authorization header for server-side authentication in App Router components.
Implementing Firebase Authentication in Next.js
Next.js applications run code on both the client and server, which means Firebase Auth requires two SDKs: the client SDK for sign-in UI and the Admin SDK for server-side token verification. This tutorial shows you how to configure both SDKs, create an AuthContext provider with onAuthStateChanged, verify ID tokens in API routes, use middleware for route protection, and implement a session cookie pattern for seamless server-side authentication in the App Router.
Prerequisites
- Next.js 14+ project with App Router
- Firebase project with Authentication enabled
- Firebase JS SDK v9+ and firebase-admin installed
- Email/Password sign-in enabled in Firebase Console
Step-by-step guide
Set up Firebase client SDK configuration
Set up Firebase client SDK configuration
Create a Firebase client configuration file that initializes the app and auth instances. This file will be imported by client components for sign-in, sign-out, and auth state listening. Store your Firebase config values in environment variables prefixed with NEXT_PUBLIC_ so they are available on the client side.
1// lib/firebase.ts2import { initializeApp, getApps } from 'firebase/app';3import { getAuth } from 'firebase/auth';45const firebaseConfig = {6 apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,7 authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,8 projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,9 storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,10 messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,11 appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,12};1314// Prevent re-initialization in hot reload15const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];16export const auth = getAuth(app);17export default app;Expected result: Firebase client SDK is initialized once and exported for use in client components.
Set up Firebase Admin SDK for server-side verification
Set up Firebase Admin SDK for server-side verification
The Admin SDK runs on the server to verify ID tokens and manage users. It requires a service account key stored as a server-side environment variable (not NEXT_PUBLIC_). Initialize it once and export the admin auth instance for use in API routes and server components.
1// lib/firebase-admin.ts2import { initializeApp, getApps, cert } from 'firebase-admin/app';3import { getAuth } from 'firebase-admin/auth';45const adminApp =6 getApps().length === 07 ? initializeApp({8 credential: cert({9 projectId: process.env.FIREBASE_PROJECT_ID,10 clientEmail: process.env.FIREBASE_CLIENT_EMAIL,11 privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),12 }),13 })14 : getApps()[0];1516export const adminAuth = getAuth(adminApp);Expected result: Firebase Admin SDK is initialized and can verify ID tokens on the server.
Create an AuthContext provider for client-side state
Create an AuthContext provider for client-side state
Build a React context that wraps onAuthStateChanged and provides the current user to all client components. Mark this as a client component with 'use client'. The provider also exposes a loading state so components can show a loading indicator while the initial auth check completes.
1'use client';23import { createContext, useContext, useEffect, useState } from 'react';4import { onAuthStateChanged, User } from 'firebase/auth';5import { auth } from '@/lib/firebase';67interface AuthContextType {8 user: User | null;9 loading: boolean;10}1112const AuthContext = createContext<AuthContextType>({13 user: null,14 loading: true,15});1617export function AuthProvider({ children }: { children: React.ReactNode }) {18 const [user, setUser] = useState<User | null>(null);19 const [loading, setLoading] = useState(true);2021 useEffect(() => {22 const unsubscribe = onAuthStateChanged(auth, (user) => {23 setUser(user);24 setLoading(false);25 });26 return unsubscribe;27 }, []);2829 return (30 <AuthContext.Provider value={{ user, loading }}>31 {children}32 </AuthContext.Provider>33 );34}3536export const useAuth = () => useContext(AuthContext);Expected result: Client components can call useAuth() to access the current user and loading state reactively.
Implement sign-in and sign-out functions
Implement sign-in and sign-out functions
Create authentication functions that handle email/password sign-in and sign-out. After a successful sign-in, get the user's ID token and store it in an HTTP-only cookie via an API route so the server can access it. This bridges client-side Firebase Auth with server-side authentication.
1'use client';23import {4 signInWithEmailAndPassword,5 createUserWithEmailAndPassword,6 signOut as firebaseSignOut,7} from 'firebase/auth';8import { auth } from '@/lib/firebase';910export async function signIn(email: string, password: string) {11 const credential = await signInWithEmailAndPassword(auth, email, password);12 const idToken = await credential.user.getIdToken();1314 // Store token in HTTP-only cookie via API route15 await fetch('/api/auth/session', {16 method: 'POST',17 headers: { 'Content-Type': 'application/json' },18 body: JSON.stringify({ idToken }),19 });2021 return credential.user;22}2324export async function signUp(email: string, password: string) {25 const credential = await createUserWithEmailAndPassword(auth, email, password);26 const idToken = await credential.user.getIdToken();2728 await fetch('/api/auth/session', {29 method: 'POST',30 headers: { 'Content-Type': 'application/json' },31 body: JSON.stringify({ idToken }),32 });3334 return credential.user;35}3637export async function signOut() {38 await firebaseSignOut(auth);39 await fetch('/api/auth/session', { method: 'DELETE' });40}Expected result: Users can sign in and sign out, with session cookies set for server-side authentication.
Create the session API route
Create the session API route
Build an API route that receives the Firebase ID token from the client, verifies it with the Admin SDK, creates a session cookie, and sets it as an HTTP-only cookie. This cookie is automatically sent with every request, enabling server-side authentication in middleware and server components.
1// app/api/auth/session/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { adminAuth } from '@/lib/firebase-admin';4import { cookies } from 'next/headers';56export async function POST(request: NextRequest) {7 const { idToken } = await request.json();89 // Verify the ID token10 try {11 await adminAuth.verifyIdToken(idToken);12 } catch {13 return NextResponse.json({ error: 'Invalid token' }, { status: 401 });14 }1516 // Create a session cookie (14-day expiry)17 const expiresIn = 60 * 60 * 24 * 14 * 1000; // 14 days18 const sessionCookie = await adminAuth.createSessionCookie(idToken, { expiresIn });1920 const cookieStore = await cookies();21 cookieStore.set('session', sessionCookie, {22 maxAge: expiresIn / 1000,23 httpOnly: true,24 secure: process.env.NODE_ENV === 'production',25 path: '/',26 sameSite: 'lax',27 });2829 return NextResponse.json({ status: 'success' });30}3132export async function DELETE() {33 const cookieStore = await cookies();34 cookieStore.delete('session');35 return NextResponse.json({ status: 'success' });36}Expected result: The API route creates a session cookie on sign-in and deletes it on sign-out.
Protect server components with session verification
Protect server components with session verification
In server components and middleware, read the session cookie and verify it with the Admin SDK. If the session is invalid or missing, redirect to the login page. This pattern works in any server component, API route, or Next.js middleware.
1// lib/auth-server.ts2import { cookies } from 'next/headers';3import { adminAuth } from '@/lib/firebase-admin';4import { DecodedIdToken } from 'firebase-admin/auth';56export async function getServerUser(): Promise<DecodedIdToken | null> {7 const cookieStore = await cookies();8 const session = cookieStore.get('session')?.value;9 if (!session) return null;1011 try {12 return await adminAuth.verifySessionCookie(session, true);13 } catch {14 return null;15 }16}1718// Usage in a server component:19// app/dashboard/page.tsx20import { redirect } from 'next/navigation';21import { getServerUser } from '@/lib/auth-server';2223export default async function DashboardPage() {24 const user = await getServerUser();25 if (!user) redirect('/login');2627 return <div>Welcome, {user.email}</div>;28}Expected result: Server components can verify the user's identity and redirect unauthenticated requests to the login page.
Complete working example
1// lib/firebase.ts — Client SDK2import { initializeApp, getApps } from 'firebase/app';3import { getAuth } from 'firebase/auth';45const firebaseConfig = {6 apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,7 authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,8 projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,9 storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,10 messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,11 appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,12};1314const app = getApps().length === 015 ? initializeApp(firebaseConfig)16 : getApps()[0];1718export const auth = getAuth(app);19export default app;2021// lib/firebase-admin.ts — Admin SDK22import { initializeApp as initAdmin, getApps as getAdminApps, cert } from 'firebase-admin/app';23import { getAuth as getAdminAuth } from 'firebase-admin/auth';2425const adminApp = getAdminApps().length === 026 ? initAdmin({27 credential: cert({28 projectId: process.env.FIREBASE_PROJECT_ID,29 clientEmail: process.env.FIREBASE_CLIENT_EMAIL,30 privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),31 }),32 })33 : getAdminApps()[0];3435export const adminAuth = getAdminAuth(adminApp);3637// lib/auth-server.ts — Server-side session helper38import { cookies } from 'next/headers';3940export async function getServerUser() {41 const cookieStore = await cookies();42 const session = cookieStore.get('session')?.value;43 if (!session) return null;44 try {45 return await adminAuth.verifySessionCookie(session, true);46 } catch {47 return null;48 }49}Common mistakes when using Firebase Auth with Next.js
Why it's a problem: Importing firebase-admin in client components, causing build errors from Node.js-only modules
How to avoid: Never import firebase-admin in files marked 'use client' or imported by client components. Keep admin imports strictly in API routes, server components, and middleware.
Why it's a problem: Using NEXT_PUBLIC_ prefix for the Firebase service account private key, exposing it to the client
How to avoid: Server-only environment variables must NOT have the NEXT_PUBLIC_ prefix. Use FIREBASE_PRIVATE_KEY without the prefix so it is only available on the server.
Why it's a problem: Relying only on client-side auth checks without server-side verification, allowing bypasses
How to avoid: Always verify the session cookie or ID token with the Admin SDK on the server. Client-side auth state can be spoofed by modifying JavaScript.
Why it's a problem: Not handling the private key newline conversion when storing it in environment variables
How to avoid: Apply .replace(/\\n/g, '\n') to convert literal \n strings back to actual newline characters in the private key.
Best practices
- Separate client SDK (lib/firebase.ts) and Admin SDK (lib/firebase-admin.ts) into different files to prevent accidental client-side imports
- Use session cookies instead of raw ID tokens for server-side auth — session cookies last up to 14 days vs 1 hour for ID tokens
- Store Firebase config in environment variables: NEXT_PUBLIC_ for client config, no prefix for server secrets
- Use the getApps().length check to prevent re-initialization during hot module replacement
- Verify session cookies with checkRevoked: true to ensure revoked sessions are rejected
- Wrap the root layout with AuthProvider to give all client components access to auth state
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Set up Firebase Auth in a Next.js 14 App Router project. Show client SDK and Admin SDK configuration, an AuthContext provider with onAuthStateChanged, email/password sign-in, a session cookie API route with verifyIdToken and createSessionCookie, and a server-side getServerUser helper.
Implement Firebase Auth for Next.js App Router with two SDKs: client-side firebase/auth for signInWithEmailAndPassword and onAuthStateChanged, and firebase-admin for verifyIdToken and createSessionCookie in API routes. Include AuthContext provider, session cookie management, and server component protection.
Frequently asked questions
Do I need both the client SDK and Admin SDK for Next.js?
Yes. The client SDK handles sign-in UI and auth state on the browser. The Admin SDK verifies tokens and manages sessions on the server. Both are needed for a secure Next.js authentication flow.
Why use session cookies instead of just sending ID tokens?
Firebase ID tokens expire after 1 hour. Session cookies created with createSessionCookie() can last up to 14 days and support revocation. They are also sent automatically with every request, unlike Authorization headers.
Can I use Firebase Auth with Next.js Server Actions?
Yes. Server Actions run on the server, so you can read the session cookie and verify it with the Admin SDK just like in API routes.
How do I handle token refresh in Next.js?
The client SDK handles token refresh automatically. For server-side, session cookies do not need refresh — they are valid for their full expiry period. Re-create the session cookie when the user explicitly signs in again.
Is Firebase Auth compatible with Next.js middleware?
Yes, but with limitations. Middleware runs on the Edge runtime, which does not support the full Admin SDK. Use a lightweight JWT verification library or call an API route from middleware for full token verification.
Can RapidDev help implement authentication in my Next.js application?
Yes. RapidDev can implement complete Firebase Auth flows in Next.js including social login, session management, role-based access control, and middleware-based route protection.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation