NextJS & Firebase Integration

Introduction to Next.js with Firebase Authentication
This project demonstrates how to integrate Firebase Authentication with a Next.js application, providing a secure and efficient way to manage user authentication. Firebase offers a robust backend-as-a-service platform, and its authentication module seamlessly handles user sign-ins, sign-outs, and session management.
Key Features:
- Next.js Middleware: Protect routes by checking user authentication status using cookies.
- Firebase Authentication: Use Firebase to manage user accounts, handle sign-in and sign-out, and monitor authentication state changes.
- Global State Management: Implement a React Context (
AuthContext) to manage and share authentication state across the application. - Environment Variables: Keep sensitive Firebase credentials secure using
.env.local.
By following this guide, you'll set up a fully functional authentication system with protected routes and dynamic user session handling, all within the simplicity and performance of Next.js. Whether you're building a small project or a large-scale application, this setup is a solid foundation for adding authentication to your app.
Project Structure
Here's the suggested structure for a project that includes Firebase authentication in Next.js:
project-root/
├── components/
│ ├── AuthProvider.jsx
├── context/
│ ├── AuthContext.jsx
├── lib/
│ ├── firebase.js
├── middleware.ts
├── pages/
│ ├── _app.js
│ ├── index.js
│ ├── login.js
│ ├── dashboard.js
├── styles/
│ ├── globals.css
├── .env.local
├── next.config.js
├── package.json
Detailed Explanation
lib/firebase.js
Your firebase.js file initializes Firebase. The code you've provided is correct and initializes the Firebase app only once. It also sets up the auth and db exports for authentication and Firestore usage.
Here's the lib/firebase.js file that initializes Firebase for your Next.js project:
// lib/firebase.js
import { initializeApp, getApps } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
// Firebase configuration using environment variables
const clientCredentials = {
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,
};
// Initialize Firebase only if no apps are already initialized
let firebaseApp;
if (!getApps().length) {
firebaseApp = initializeApp(clientCredentials);
} else {
firebaseApp = getApps()[0];
}
// Export Firebase Auth and Firestore instances
export const auth = getAuth(firebaseApp);
export const db = getFirestore(firebaseApp);
Explanation:
Environment Variables:
- The configuration values (API key, Auth domain, etc.) are stored in
.env.localand accessed usingprocess.env. - This ensures sensitive information is not exposed in your codebase.
- The configuration values (API key, Auth domain, etc.) are stored in
Firebase Initialization:
- The
initializeAppfunction is called only if no Firebase apps are already initialized. This avoids reinitialization errors, which can occur during development due to hot reloading.
- The
Exported Modules:
auth: Provides Firebase Authentication features.db: Connects to Firestore, Firebase's NoSQL database.
This file will serve as the core configuration for Firebase in your application. You can import auth and db from this file whenever you need to use Firebase Authentication or Firestore in your project.
context/AuthContext.jsx
This file will manage the authentication state using React Context.
import { createContext, useContext, useState, useEffect } from "react";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "../lib/firebase";
const AuthContext = createContext();
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
return (
<AuthContext.Provider value={{ user, loading }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
return useContext(AuthContext);
}
middleware.ts
Your middleware ensures users cannot access protected pages without logging in.
The code you've provided works perfectly. Just ensure cookies like isLoggedIn are set properly during login.
Here's the middleware.ts file for your Next.js project:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Check if the user is authenticated by looking for the 'isLoggedIn' cookie
const isLoggedIn = request.cookies.get("isLoggedIn");
console.log("Authentication status (isLoggedIn):", isLoggedIn);
// Redirect to /login if the user is not logged in and tries to access protected routes
if (!isLoggedIn && request.nextUrl.pathname !== "/login") {
return NextResponse.redirect(new URL("/login", request.url));
}
// Proceed to the requested route if the user is authenticated or on the login page
return NextResponse.next();
}
// Apply middleware to all routes except /login
export const config = {
matcher: ["/((?!login).*)"], // Matches all routes except /login
};
Explanation:
isLoggedInCookie:- The middleware checks for a cookie named
isLoggedIn, which should be set after a successful login.
- The middleware checks for a cookie named
Redirection:
- If the cookie is not present and the user is attempting to access a route other than
/login, they are redirected to the/loginpage.
- If the cookie is not present and the user is attempting to access a route other than
NextResponse.next():- Allows the request to proceed if the user is authenticated or accessing
/login.
- Allows the request to proceed if the user is authenticated or accessing
matcherConfiguration:- Ensures the middleware runs for all routes except
/login.
- Ensures the middleware runs for all routes except
pages/_app.js
Wrap your application with the AuthProvider.
import { AuthProvider } from "../context/AuthContext";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
);
}
export default MyApp;
pages/login.js
Create a login page using Firebase Authentication.
import { useState } from "react";
import { signInWithEmailAndPassword } from "firebase/auth";
import { useRouter } from "next/router";
import { auth } from "../lib/firebase";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const router = useRouter();
const handleLogin = async (e) => {
e.preventDefault();
try {
await signInWithEmailAndPassword(auth, email, password);
document.cookie = "isLoggedIn=true"; // Set the cookie
router.push("/dashboard");
} catch (err) {
setError("Invalid credentials");
}
};
return (
<div>
<h1>Login</h1>
{error && <p>{error}</p>}
<form onSubmit={handleLogin}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<button type="submit">Login</button>
</form>
</div>
);
}
pages/dashboard.js
A protected page that requires authentication.
import { useAuth } from "../context/AuthContext";
import { useRouter } from "next/router";
export default function Dashboard() {
const { user, loading } = useAuth();
const router = useRouter();
if (loading) return <p>Loading...</p>;
if (!user) {
router.push("/login");
return null;
}
return (
<div>
<h1>Welcome, {user.email}</h1>
<button
onClick={() => {
auth.signOut();
document.cookie = "isLoggedIn=; Max-Age=0"; // Clear the cookie
router.push("/login");
}}
>
Logout
</button>
</div>
);
}
.env.local
Environment variables for Firebase configuration.
NEXT_PUBLIC_FIREBASE_API_KEY=your-api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-auth-domain
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-storage-bucket
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your-messaging-sender-id
NEXT_PUBLIC_FIREBASE_APP_ID=your-app-id
Key Features of This Setup
- Firebase Authentication: Using
AuthContextto manage authentication globally. - Middleware: Protects routes using
isLoggedIncookie. - Environment Variables: Ensures sensitive keys are not hard-coded.
- Protected Routes: The dashboard only allows access if the user is authenticated.
This structure provides a secure and scalable authentication system for your Next.js project using Firebase.

