DEV Community

Cover image for How to use Firebase Authentication in Next.js 13, Server Side with Admin SDK
Geiel Peguero
Geiel Peguero

Posted on

How to use Firebase Authentication in Next.js 13, Server Side with Admin SDK

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
Enter fullscreen mode Exit fullscreen mode

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 Firebase settings
  • Go to Service accounts - Firebase Admin SDK then Generate new private key

generate 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);
    }
}

Enter fullscreen mode Exit fullscreen mode

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"
      })
}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

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 });
}
Enter fullscreen mode Exit fullscreen mode

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 });
}
Enter fullscreen mode Exit fullscreen mode

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*"],
};
Enter fullscreen mode Exit fullscreen mode

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 });
}
Enter fullscreen mode Exit fullscreen mode

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");
    }
  }
Enter fullscreen mode Exit fullscreen mode

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 (16)

Collapse
 
victorfu profile image
Victor Fu

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.

await fetch(`${request.nextUrl.origin}/api/login`)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
estevanulian profile image
Estevan Ulian

Thanks for the tip.

Collapse
 
ibrahimraimi profile image
Ibrahim Raimi

Great article, Geiel. I love the fact that you included screenshots for better understanding.

Collapse
 
geiel profile image
Geiel Peguero

Thank you Ibrahim

Collapse
 
maxwissink profile image
maxwissink

When deploying this to firebase, i get an 500 internal server error, can you confirm that this solution works on your end? thanks

Collapse
 
aayman997 profile image
Ahmed Ayman

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

Collapse
 
marcellintacite profile image
Aksanti Bahiga tacite

Thank you for the article, but i would like to know , how do i get current user information?

Collapse
 
3akare profile image
David Bakare

auth.currentUser.[information]

Collapse
 
abhirup2003 profile image
Abhirup Kumar Bhowmick

I tried this way, but even after succesfull login, auth.currentUser is null.

Collapse
 
3akare profile image
David Bakare

You are the best!

Collapse
 
itssidhere profile image
Sapien

Great Article!

Collapse
 
zainuldin profile image
Zain_ul_din

How to retrieve user credentials on the client side once the user login and reloads the page?

Collapse
 
drnyt profile image
Shehryar

Just what I needed, amazing article!

Collapse
 
jikkeee profile image
jikkeee

thanks man

Collapse
 
vic_ble profile image
victordelva

Thank you! It was helpful!

Collapse
 
estevanulian profile image
Estevan Ulian

It's a great article. Thank you.