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 SDKnpm install firebase firebase-adminEnter fullscreen modeExit fullscreen modeThere 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 firebaseIn 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 settingsGo to Service accounts - Firebase Admin SDK then Generate new private keyFirebase will give you .json file with you credentialsNow with you credentials we can go the Next.js project.
Configure Firebase Admin SDKCreate a /lib/firebase-admin-config.ts file in the project base routeimport { initializeApp, getApps, cert } from 'firebase-admin/app';const firebaseAdminConfig = {credential: cert(process.env.FIREBASE_SECRET_KEY)}export function customInitApp() {if (getApps().length 0 ? getApp() : initializeApp(firebaseConfig);const auth = getAuth(app);const provider = new GoogleAuthProvider();export {auth, provider}Enter fullscreen modeExit fullscreen modeYou 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 clientCreate a /app/login/page.tsx fileHere 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 (signIn()}>Sign In );}Enter fullscreen modeExit fullscreen mode Sign In in the serverCreate a /app/api/login/route.tsx fileHere we are gonna handle the users sessions and authorize it using Firebase Adminimport { 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 calledcustomInitApp();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 });}Enter fullscreen modeExit fullscreen modeWith 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 serverYou 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 });}Enter fullscreen modeExit fullscreen modeNow you can call this method anywhere in your app, for example in the middleware.ts file
Validate user session in the middleware.tsimport { 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 routesexport const config = { matcher: ["/protected/:path*"],};Enter fullscreen modeExit fullscreen mode Sign out a userIf 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 });}Enter fullscreen modeExit fullscreen modeNow 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 clientawait signOut(auth);//Clear the cookies in the serverconst response = await fetch("http://localhost:3000/api/signOut", { method: "POST",});if (response.status === 200) { router.push("/login");} }Enter fullscreen modeExit fullscreen mode ConclusionNow 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.