Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

13.x has broken auto finding service account for auth.createCustomToken #2800

Open
seaders opened this issue Dec 4, 2024 · 8 comments · May be fixed by #2801
Open

13.x has broken auto finding service account for auth.createCustomToken #2800

seaders opened this issue Dec 4, 2024 · 8 comments · May be fixed by #2801

Comments

@seaders
Copy link

seaders commented Dec 4, 2024

[REQUIRED] Step 2: Describe your environment

  • Operating System version: OSX 15.1.1
  • Firebase SDK version: 13.0.1
  • Firebase Product: auth (auth, database, storage, etc)
  • Node.js version: 20.11.1
  • NPM version: 10.9.1

[REQUIRED] Step 3: Describe the problem

Steps to reproduce:

What happened? How can we make the problem occur?

Prior to 13.x and the switch to google auth, you were able to run code like this

import { initializeApp } from "firebase-admin/app"
import { getAuth } from "firebase-admin/auth"

initializeApp()
getAuth().createCustomToken("test")

and create a custom token fine. I was able to do it locally, without error.

Now, we get errors locally like

Failed to determine service account. Make sure to initialize the SDK with a service account credential. Alternatively specify a service account with iam.serviceAccounts.signBlob permission. Original error: Error: Error while making request: getaddrinfo ENOTFOUND metadata. Error code: ENOTFOUND

This is very similar to the hoops introduced in 13.x to get the projectId.

To get everything properly working with each other, you need to do something like unnecessarily create the google auth, wait for the projectId/for it to be "filled", and then set the service account that that had auto set in the initializeApp function,

import { initializeApp } from "firebase-admin/app"
import { GoogleAuth } from "google-auth-library"

const gAuth = new GoogleAuth()
const projectId = await gAuth.getProjectId()
const jsonContent = gAuth.jsonContent as { client_email: string }
const app = initializeApp({ serviceAccountId: jsonContent.client_email })

All of the above seems like a big regression to many working features which needs to be fixed, or much of the documentation needs updating as running just initializeApp now does not set up your app fully like it did prior to 13.x, or plainly just a bug.

--- edit ---

My creds are loaded from my .env file, via

GOOGLE_APPLICATION_CREDENTIALS=/Users/Shared/PATH_TO_SERVICE_FILE.json

The exact same service account and env vars are set, when testing against pre 13 and 13.x, and works fine with the appropriate code above, no issue with env vars, nor service account perms.

@lahirumaramba
Copy link
Member

Hey @seaders ,

When you call initializeApp() with no parameters the SDK will try to fetch the credentials from your environment.
How are you setting the service account credentials in your local environment? Do you use the GOOGLE_APPLICATION_CREDENTIALS env variable?

Does your service account have required permissions? See: https://firebase.google.com/docs/auth/admin/create-custom-tokens#service_account_does_not_have_required_permissions

Re: the project id, could you try the accessing the credentials in your app instance without using GoogleAuth?

const app = initializeApp()
const projectId = app.options.credential.projectId

@lahirumaramba lahirumaramba self-assigned this Dec 4, 2024
@seaders
Copy link
Author

seaders commented Dec 4, 2024

Hi @lahirumaramba I should have mentioned that in the OP, but yeah,

.env file,

GOOGLE_APPLICATION_CREDENTIALS=/Users/Shared/.json

In regards the perms, as I mentioned in the post, this works perfectly, with the same service account, service account json file, and .env vars and the above code, on pre 13.x, and with the altered code on 13, so it's 100% nothing to do with the perms. I think it's to do with how the app is initialising now, and due to the "promise-y" nature of loading GoogleAuth, things aren't setting up right.

In regards projectId... my dude
image

As per the docs,

export interface Credential {

projectId doesn't exist on the Credential type. Even if I DID force cast it as having a projectId, it's still empty. It's empty until the getProjectId is awaited upon, in the private (inaccessible to even cast as) ApplicationDefaultCredential

public async getProjectId(): Promise<string> {

I truly don't understand the strategy that's gone on with some of this stuff and the change to GoogleAuth. Either expose the googleAuth object on the credential, or expose the internals of the default credential, or at least let us check, and cast the credential as the internal class. I'm really surprised that more things hasn't broken for more people, we've had a nightmare with the change.

@dipbhi
Copy link

dipbhi commented Dec 5, 2024

Just using initializeApp() doesn't work anymore when creating a custom token. Downgrading to 2.7.0 works.

This is going to break a lot of code (and hearts).

@lahirumaramba
Copy link
Member

lahirumaramba commented Dec 5, 2024

I understand your frustration and thanks for filing this issue. I think I found the root cause and we are working on a fix in #2801

In the meantime, could you try the following as a workaround?

import { cert, initializeApp } from "firebase-admin/app"
import { getAuth } from "firebase-admin/auth"

const app = initializeApp({
    credential: cert(process.env['GOOGLE_APPLICATION_CREDENTIALS'])
})

await getAuth().createCustomToken("test")

Using cert() for now will force the SDK to create an instance of a ServiceAccount credential, which should prevent the error thrown in the crypto signer you are currently seeing.

If you use the workaround mentioned above you should also be able to access the project id through:

(app.options.credential as any).projectId

We will also look into how we can better expose the project ID and service account in the future (and the internal Google Auth type) once this issue is fixed.

@seaders
Copy link
Author

seaders commented Dec 5, 2024

Thanks for the help @lahirumaramba

I've just tried that locally now, and can confirm that that solves both the token creation and the projectId bit.

Unfortunately that doesn't work when deployed we get,

image

from the code

import admin from "firebase-admin"

export const credsLoc = process.env.GOOGLE_APPLICATION_CREDENTIALS
console.log({ credsLoc })
export const firebase =
  admin.apps[0] ??
  admin.initializeApp({
    credential: admin.credential.cert(credsLoc!)
  })

const projectId = (firebase.options.credential as any).projectId
console.log({ projectId })

@lahirumaramba
Copy link
Member

lahirumaramba commented Dec 5, 2024

That makes sense on Cloud Run you don't have the env var set (which you don't have to) so the SDK will look for Application Default Credentials in the environment. Can you try the following on your Cloud Run (GCP environment):

const app = admin.initializeApp()

await (app.options.credential as any).getProjectId()
await (app.options.credential as any).getServiceAccountEmail()

@seaders
Copy link
Author

seaders commented Dec 5, 2024

Yep that works

image

But this goes to my earlier point about how unstable it is casting that as any because all these kinda essential things are hidden from there, while completely and easily available via the GoogleAuth instance that you can just create yourself.

@lahirumaramba
Copy link
Member

Yes, I agree. That is why I mention this (casting to any) as a workaround in my comment above. We will look into better exposing getters for the internal GoogleAuth type in the future as an improvement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants