I'm new to this world of Full Stack Frameworks like Next.js, SvelteKit, Remix... But I know all the advantages it has so I wanted to use it to create a project I'm working on.
I love Firebase and I wanted to have it in the project, because it just has everything I need for almost any project, the problem I had is that Next.js although it is great, the update to version 13 is something recent so it was difficult to find information about integrating Firebase Authentication in Next.js, so in this tutorial I will show how to implement Firebase with Next.js 13.
Install Firebase and Firebase Admin SDK
npm install firebase firebase-admin
There are many ways to manage authentication with Firebase, I will give you the basics so that you can implement it as you wish in your application.
First we need to get our private key from firebase
In order to access to firebase from the server and use the Admin SDK we need to generate a private key from Google.
- Go to Project Overview then Project settings
- Go to Service accounts - Firebase Admin SDK then Generate new private key
- Firebase will give you .json file with you credentials
Now with you credentials we can go the Next.js project.
Configure Firebase Admin SDK
- Create a
/lib/firebase-admin-config.ts
file in the project base route
import { initializeApp, getApps, cert } from 'firebase-admin/app';
const firebaseAdminConfig = {
credential: cert(process.env.FIREBASE_SECRET_KEY)
}
export function customInitApp() {
if (getApps().length <= 0) {
initializeApp(firebaseAdminConfig);
}
}
The FIREBASE_SECRET_KEY
is the file location that we download from firebase, if you don't want to use the file you can pass the credentials directly like this.
const firebaseAdminConfig = {
credential: cert({
projectId: "project_id property from the file",
clientEmail: "client_email property from the file",
privateKey: "private_key property from the file"
})
}
With this file we have the Firebase Admin SDK configured in our project.
Now let's configure our Firebase web app.
Configure Firebase Web App
- Create a
/lib/firebase-config.ts
file in the project base route
import { initializeApp } from "firebase/app";
import { getApps, getApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { GoogleAuthProvider } from "firebase/auth";
const firebaseConfig = {
apiKey: "process.env.FIREBASE_API_KEY",
authDomain: "process.env.FIREBASE_AUTH_DOMAIN",
projectId: "process.env.FIREBASE_PROJECT_ID",
storageBucket: "process.env.FIREBASE_STORAGE_BUCKET",
messagingSenderId: "process.env.FIREBASE_MESSAGING_SENDER_ID",
appId: "process.env.FIREBASE_APP_ID",
measurementId: "process.env.FIREBASE_MEASUREMENT_ID"
};
const app = getApps().length > 0 ? getApp() : initializeApp(firebaseConfig);
const auth = getAuth(app);
const provider = new GoogleAuthProvider();
export {auth, provider}
You can find all those configuration in the Firebase Console go the Firebase documentation for more information.
Im using the Google Provider to authenticate users, but you can use whatever firebase offer.
Now the main reason why I'm doing this tutorial is basically to let you know how to authenticate users in the server side using only Firebase, so let's get started.
You can use Route Handlers to manage the user session in the server
Sign In user with Firebase
Sign In in the client
- Create a
/app/login/page.tsx
file Here we are gonna use Firebase to Sign In with Google and get the token_id that later we are gonna send to the server
"use client";
import { getRedirectResult, signInWithRedirect } from "firebase/auth";
import { auth, provider } from "../../lib/firebase";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
export default function SignIn() {
const router = useRouter();
useEffect(() => {
getRedirectResult(auth).then(async (userCred) => {
if (!userCred) {
return;
}
fetch("/api/login", {
method: "POST",
headers: {
Authorization: `Bearer ${await userCred.user.getIdToken()}`,
},
}).then((response) => {
if (response.status === 200) {
router.push("/protected");
}
});
});
}, []);
function signIn() {
signInWithRedirect(auth, provider);
}
return (
<>
<button onClick={() => signIn()}>Sign In</button>
</>
);
}
Sign In in the server
- Create a
/app/api/login/route.tsx
file Here we are gonna handle the users sessions and authorize it using Firebase Admin
import { auth } from "firebase-admin";
import { customInitApp } from "@/lib/firebase-admin-config";
import { cookies, headers } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
// Init the Firebase SDK every time the server is called
customInitApp();
export async function POST(request: NextRequest, response: NextResponse) {
const authorization = headers().get("Authorization");
if (authorization?.startsWith("Bearer ")) {
const idToken = authorization.split("Bearer ")[1];
const decodedToken = await auth().verifyIdToken(idToken);
if (decodedToken) {
//Generate session cookie
const expiresIn = 60 * 60 * 24 * 5 * 1000;
const sessionCookie = await auth().createSessionCookie(idToken, {
expiresIn,
});
const options = {
name: "session",
value: sessionCookie,
maxAge: expiresIn,
httpOnly: true,
secure: true,
};
//Add the cookie to the browser
cookies().set(options);
}
}
return NextResponse.json({}, { status: 200 });
}
With this you already have the user logged in with Firebase using Google, now the advantage of using this approach is that you have access to the session cookie, which is basically the token of the logged user, and you can access it from the server, so if you need to SSR and authenticate the user you can do it.
How to authenticate a user in the server
You can create in the same /app/api/login/route.tsx
file a new method to check if the user is authenticated or not
export async function GET(request: NextRequest) {
const session = cookies().get("session")?.value || "";
//Validate if the cookie exist in the request
if (!session) {
return NextResponse.json({ isLogged: false }, { status: 401 });
}
//Use Firebase Admin to validate the session cookie
const decodedClaims = await auth().verifySessionCookie(session, true);
if (!decodedClaims) {
return NextResponse.json({ isLogged: false }, { status: 401 });
}
return NextResponse.json({ isLogged: true }, { status: 200 });
}
Now you can call this method anywhere in your app, for example in the middleware.ts
file
Validate user session in the middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export async function middleware(request: NextRequest, response: NextResponse) {
const session = request.cookies.get("session");
//Return to /login if don't have a session
if (!session) {
return NextResponse.redirect(new URL("/login", request.url));
}
//Call the authentication endpoint
const responseAPI = await fetch("/api/login", {
headers: {
Cookie: `session=${session?.value}`,
},
});
//Return to /login if token is not authorized
if (responseAPI.status !== 200) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
//Add your protected routes
export const config = {
matcher: ["/protected/:path*"],
};
Sign out a user
If your cookies are httpOnly, the only way to delete them is on the server, so you can create a handler for that.
import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
//Remove the value and expire the cookie
const options = {
name: "session",
value: "",
maxAge: -1,
};
cookies().set(options);
return NextResponse.json({}, { status: 200 });
}
Now in the client you can call that endpoint
import { auth } from "@/lib/firebase";
import { signOut } from "firebase/auth";
async function signOutUser() {
//Sign out with the Firebase client
await signOut(auth);
//Clear the cookies in the server
const response = await fetch("http://localhost:3000/api/signOut", {
method: "POST",
});
if (response.status === 200) {
router.push("/login");
}
}
Conclusion
Now with this you have the bases to be able to authenticate with Firebase anywhere in your application, you can implement this to your liking, since Firebase Admin gives you the flexibility to do it, here is only one, but I am sure that you can implement something even better and that suits your situation.
Top comments (17)
Using
await fetch("/api/login")
in the middleware doesn't work. It will throw an error: Failed to parse URL. I found a working solution for my case. FYI.Thanks for the tip.
Great article, Geiel. I love the fact that you included screenshots for better understanding.
Thank you Ibrahim
Thank you for the article, but i would like to know , how do i get current user information?
auth.currentUser.[information]
When deploying this to firebase, i get an 500 internal server error, can you confirm that this solution works on your end? thanks
Yes same for me I get an error
The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services
Great Article!
You are the best!
How to retrieve user credentials on the client side once the user login and reloads the page?
Just what I needed, amazing article!
thanks man
It's a great article. Thank you.