'How to configure firebase-admin-sdk for `verifyIdToken` to pass?

I try to use Firebase in my application. The frontend logs the user in using the Web SDK, without any backend. Later, I would like to call some backend APIs. For this reason, I pass the idToken to the backend and try to validate the user as described in the Firebase docs.

When I do the above flow locally using the Firebase Emulator everything works as expected.

When I switch off the Emulator the idToken validation fails with

{
  errorInfo: {
    code: 'auth/argument-error',
    message: 'Firebase ID token has invalid signature. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'
  },
  codePrefix: 'auth'
}

I created a Google hosted Firebase function to check if I can get the idToken validated there. The above setup works when the validation happens within the Google infrastructure.

Based on the above, I think the issue is in my FirebaseApp setup in the API. What that issue might be?

This is my setup.

I define 3 environment variables:

FIREBASE_DB_URL=https://<project-id>.firebaseio.com
FIREBASE_PROJECT_ID=<project-id>
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

I checked and cat $GOOGLE_APPLICATION_CREDENTIALS prints the correct file.

I initialize Firebase in the API with

import admin from "firebase-admin";

if(admin.apps.length == 0) {
  admin.initializeApp({
    credential: admin.credential.applicationDefault(),
    databaseURL: process.env.FIREBASE_DB_URL,
    projectId: process.env.FIREBASE_PROJECT_ID,
  });
  console.log('Firebase initialized')
} else {
  console.warn('Firebase already initialized')
}

and this is the validating code

import { DecodedIdToken } from 'firebase-admin/lib/auth/token-verifier';
import { getAuth } from 'firebase-admin/auth';
import './initializeFirebase';

export default async function needsLoggedInUser(idToken: string): Promise<DecodedIdToken|false> {
  try {
    return await getAuth().verifyIdToken(idToken)
  } catch(err) {
    console.error(err)
    return false
  }
}

I use the above in a NextJS API code as

import { NextApiRequest, NextApiResponse } from 'next'
import { getDatabase } from 'firebase-admin/database';
import 'services/backend/initializeFirebase';
import needsLoggedInUser from 'services/backend/needsLoggedInUser';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // As an admin, the app has access to read and write all data, regardless of Security Rules
  const decodedToken = await needsLoggedInUser(req.body.user)
  if(!decodedToken) {
    return res.status(403).send("403 Forbidden")
  }

  /// ... rest of the API
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source