All Articles

JWT Authentication in Node.js using Firebase tokens

Published by Tony Vu on Dec 23, 2020

If you are already using Firebase Authentication, this article show you how to protect your Node.JS or Express API endpoints from unauthorized access using Firebase ID tokens.

Firebase ID tokens are automatically generated when a user successfully authenticates using Firebase Authentication. These tokens are simply JSON web tokens that Firebase generates for you.

For the purposes of this article, I’m assuming you know how to setup Firebase on the client side and a Node.js or Express server.

At a high level, you will need to do the following

  • On the server side, add the Firebase Admin SDK to verify Firebase ID tokens
  • On the client side, use Firebase to retrieve a Firebase ID token
  • Add the Firebase ID token to your requests

To get started, the first step is to install the Firebase Admin SDK to validate these tokens. You will be using it in your API routes.

Add the Firebase Admin SDK

Simply follow the instructions to add the Firebase Admin SDK to your server on Firebase’s website. In the process of following these instructions, you will be asked to generate a private key file in JSON format.

Firebase Service Account

Specifically, this file contains a private key that is used to authorize your Node.js server access to Firebase services. Specifically, you will need this private key in order to access verify tokens passed to you in requests. The file also contains a project ID, and client email which you will use in the next step to initialize the SDK.

Firebase Private Key File

Verify the JWT tokens using the Firebase Admin SDK

Assuming you have the Firebase Admin SDK installed at this point, you will need to import it into your server.js or whichever file you have specified that will contain your API routes.

import * as admin from “firebase-admin”;

Following the import, initialize the SDK

admin.initializeApp({
  credential: admin.credential.cert({
    private_key:
      process.env.FIREBASE_ADMIN_PRIVATE_KEY[0] ===-? process.env.FIREBASE_ADMIN_PRIVATE_KEY
        : JSON.parse(process.env.FIREBASE_ADMIN_PRIVATE_KEY),
    client_email: process.env.FIREBASE_ADMIN_CLIENT_EMAIL,
    project_id: process.env.FIREBASE_ADMIN_PROJECT_ID,
  }),
  databaseURL: process.env.FIREBASE_DATABASE_URL,
});

If you deploy your Node.js backend to a cloud hosting provider like Heroku, you will want to store this key in an environment variable like I’ve done below to prevent it from being exposed in the wild. Storing the private key as an environment variable is more secure than passing the path to the JSON file containing the key. You should also add a conditional check, as I’ve done above, in case the value of the environment variable does not parse correctly as JSON when deployed to a production cloud provider like Heroku. If your private key is incorrect it will result in a Failed to parse private key: Error: Invalid PEM formatted message. error message when you try to run this code.

FIREBASE_ADMIN_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"

Now that you’ve initalized the Firebase Admin SDK, you can call the verifyIDToken() method to check if a token is valid whenever a request is made to one of your API routes. In this code snippet below, notice that I’m pulling the Firebase user ID token off of the authorization header.

const authHeader = req.headers.authorization;

if (authHeader) {
  const idToken = authHeader.split(" ")[1];
  admin
    .auth()
    .verifyIdToken(idToken)
    .then(function (decodedToken) {
      // Further logic performed by your API route here
    })
    .catch(function (error) {
      console.log(error);
      return res.sendStatus(403);
    });
} else {
  res.sendStatus(401);
}

Use Firebase getIdToken() on the client side

From my client side app, you can get the firebase ID token once a user authenticates with Firebase by calling the getIdToken() method on your Firebase user object. The user object is returned to you whensomeone successfully signs in with Firebase authentication. There are tons of articles out there that explain how to setup Firebase authentication in a client side React app if you need further guidance. To keep this article focused on just API route security, I assume you already have Firebase authentication setup in your client source code. Notice that I simply pass in the user ID token to the authorization header when I hit the endpoint using fetch.

Passing in true to `getIdToken() tells Firebase to refresh the token when retrieving the user’s ID token. By default the token will expire after a user sessions has lasted for one hour. This is important because an expired token will lIf a user logs out and logs in again a refresh will automatically be triggered.

const firebaseUserIdToken = await user.getIdToken(true);
const response = await fetch("/api/hello", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer " + firebaseUserIdToken,
  },
});

Secure you API routes using a middleware function

To keep things DRY or avoid having to repeat this in every route, move this to a middleware function that you can call in each method. If a token is valid, simply call next() to continue of execution of our API route.

const authenticateJWT = async (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (authHeader) {
    const idToken = authHeader.split(" ")[1];
    admin
      .auth()
      .verifyIdToken(idToken)
      .then(function (decodedToken) {
        return next();
      })
      .catch(function (error) {
        console.log(error);
        return res.sendStatus(403);
      });
  } else {
    res.sendStatus(401);
  }
};

The last step is to make sure you verify ID tokens before an API route can be called. You can do this by adding it into one of your API routes.

app.post(/api/token”, authenticateJWT, (req, res) => {
	...
});

You can use this middleware function in any of your API routes to ensure that a valid token is being passed with each request to a given route.

Did this post help you or do you have any comments or questions? Email me at hey@tonyvu.co or Tweet me at @tonyv00