How to use Firebase Authentication in Next.js 13, Server Side with Admin SDK
Tags: react, nextjs, firebase, tutorial
Published: 2023-05-27 20:20:49

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
![Firebase settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j90hv5pf0yhhpdvb9ng3.png)
- Go to Service accounts - Firebase Admin SDK then **Generate new private key**

![generate private key](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zuapc9oakjlzlu3uoojn.png)

- 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

```javascript
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.

```javascript
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

```javascript
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](https://firebase.google.com/docs/auth/web/start) 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](https://nextjs.org/docs/app/building-your-application/routing/router-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

```javascript
"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

```javascript
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

```javascript
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](https://nextjs.org/docs/pages/building-your-application/routing/middleware)

```javascript
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.

```javascript
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

```javascript
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.