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

How to Use Firebase Auth with Next.js

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.

What you'll learn

  • How to set up Firebase client SDK and Admin SDK in a Next.js project
  • How to build an AuthContext provider for client-side auth state management
  • How to verify Firebase ID tokens in API routes and middleware for server-side auth
  • How to protect server components and API routes with session cookies
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate8 min read20-25 minNext.js 14+ (App Router), Firebase JS SDK v9+, Firebase Admin SDK 12+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1// lib/firebase.ts
2import { initializeApp, getApps } from 'firebase/app';
3import { getAuth } from 'firebase/auth';
4
5const 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};
13
14// Prevent re-initialization in hot reload
15const 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.

2

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.

typescript
1// lib/firebase-admin.ts
2import { initializeApp, getApps, cert } from 'firebase-admin/app';
3import { getAuth } from 'firebase-admin/auth';
4
5const adminApp =
6 getApps().length === 0
7 ? 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];
15
16export const adminAuth = getAuth(adminApp);

Expected result: Firebase Admin SDK is initialized and can verify ID tokens on the server.

3

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.

typescript
1'use client';
2
3import { createContext, useContext, useEffect, useState } from 'react';
4import { onAuthStateChanged, User } from 'firebase/auth';
5import { auth } from '@/lib/firebase';
6
7interface AuthContextType {
8 user: User | null;
9 loading: boolean;
10}
11
12const AuthContext = createContext<AuthContextType>({
13 user: null,
14 loading: true,
15});
16
17export function AuthProvider({ children }: { children: React.ReactNode }) {
18 const [user, setUser] = useState<User | null>(null);
19 const [loading, setLoading] = useState(true);
20
21 useEffect(() => {
22 const unsubscribe = onAuthStateChanged(auth, (user) => {
23 setUser(user);
24 setLoading(false);
25 });
26 return unsubscribe;
27 }, []);
28
29 return (
30 <AuthContext.Provider value={{ user, loading }}>
31 {children}
32 </AuthContext.Provider>
33 );
34}
35
36export const useAuth = () => useContext(AuthContext);

Expected result: Client components can call useAuth() to access the current user and loading state reactively.

4

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.

typescript
1'use client';
2
3import {
4 signInWithEmailAndPassword,
5 createUserWithEmailAndPassword,
6 signOut as firebaseSignOut,
7} from 'firebase/auth';
8import { auth } from '@/lib/firebase';
9
10export async function signIn(email: string, password: string) {
11 const credential = await signInWithEmailAndPassword(auth, email, password);
12 const idToken = await credential.user.getIdToken();
13
14 // Store token in HTTP-only cookie via API route
15 await fetch('/api/auth/session', {
16 method: 'POST',
17 headers: { 'Content-Type': 'application/json' },
18 body: JSON.stringify({ idToken }),
19 });
20
21 return credential.user;
22}
23
24export async function signUp(email: string, password: string) {
25 const credential = await createUserWithEmailAndPassword(auth, email, password);
26 const idToken = await credential.user.getIdToken();
27
28 await fetch('/api/auth/session', {
29 method: 'POST',
30 headers: { 'Content-Type': 'application/json' },
31 body: JSON.stringify({ idToken }),
32 });
33
34 return credential.user;
35}
36
37export 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.

5

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.

typescript
1// app/api/auth/session/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { adminAuth } from '@/lib/firebase-admin';
4import { cookies } from 'next/headers';
5
6export async function POST(request: NextRequest) {
7 const { idToken } = await request.json();
8
9 // Verify the ID token
10 try {
11 await adminAuth.verifyIdToken(idToken);
12 } catch {
13 return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
14 }
15
16 // Create a session cookie (14-day expiry)
17 const expiresIn = 60 * 60 * 24 * 14 * 1000; // 14 days
18 const sessionCookie = await adminAuth.createSessionCookie(idToken, { expiresIn });
19
20 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 });
28
29 return NextResponse.json({ status: 'success' });
30}
31
32export 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.

6

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.

typescript
1// lib/auth-server.ts
2import { cookies } from 'next/headers';
3import { adminAuth } from '@/lib/firebase-admin';
4import { DecodedIdToken } from 'firebase-admin/auth';
5
6export async function getServerUser(): Promise<DecodedIdToken | null> {
7 const cookieStore = await cookies();
8 const session = cookieStore.get('session')?.value;
9 if (!session) return null;
10
11 try {
12 return await adminAuth.verifySessionCookie(session, true);
13 } catch {
14 return null;
15 }
16}
17
18// Usage in a server component:
19// app/dashboard/page.tsx
20import { redirect } from 'next/navigation';
21import { getServerUser } from '@/lib/auth-server';
22
23export default async function DashboardPage() {
24 const user = await getServerUser();
25 if (!user) redirect('/login');
26
27 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

lib/firebase.ts
1// lib/firebase.ts — Client SDK
2import { initializeApp, getApps } from 'firebase/app';
3import { getAuth } from 'firebase/auth';
4
5const 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};
13
14const app = getApps().length === 0
15 ? initializeApp(firebaseConfig)
16 : getApps()[0];
17
18export const auth = getAuth(app);
19export default app;
20
21// lib/firebase-admin.ts — Admin SDK
22import { initializeApp as initAdmin, getApps as getAdminApps, cert } from 'firebase-admin/app';
23import { getAuth as getAdminAuth } from 'firebase-admin/auth';
24
25const adminApp = getAdminApps().length === 0
26 ? 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];
34
35export const adminAuth = getAdminAuth(adminApp);
36
37// lib/auth-server.ts — Server-side session helper
38import { cookies } from 'next/headers';
39
40export 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.

ChatGPT Prompt

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.

Firebase Prompt

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.

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.