Firebase invalid-credential 302 on initializeApp() in Cloud Functions Shell - node.js

In order to speed up my development/testing, I am trying to get started with using the Cloud Functions:shell instead of deploying --only functions everytime I make a tiny change.
I am getting an 'invalid-credentials' error though.
This is a basic working index.js file:
const express = require("express");
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const app = express();
app.get("/say_hi", (req, res) => console.log("HI!"));
exports.oauth = functions.https.onRequest(app);
Here is how I call the function from the shell in the CLI:
> firebase functions:shell
> oauth.get("say_hi")
Output is "HI!" written to the console, which is good.
The problem is when I try using admin.database() as in the following function:
app.get("/get_ref", (req, res) => admin.database().ref("users"));
This is the output error:
"code": "app/invalid-credential",
"message": "Credential implementation provided to initializeApp() via the 'credential' property failed to fetch a valid Google OAuth2 access token with the following error: 'Error fetching access token: 302 Found'"
I've read the docs for '302 found' code ad 'invalid-credentials', but this doesn't tell me much about solving the issue, so I started wondering if I need a service account to initialize the Admin SDK, but that would not make sense as when I run deploy the functions run seamlessly and I can read/write to/from the Realtime DB.
What is the issue here?

Have you tried to set the GOOGLE_APPLICATION_CREDENTIALS env variable? it probably would solved your issue: https://firebase.google.com/docs/functions/local-emulator
and looking at this Use of Service account credentials when using Cloud Functions Shell might be helpful in this case.

Related

Application Default Credentials http trigger GCP function from local nodejs application

I want to trigger a GCP cloud function from a simple nodejs app running locally.
Reading the documentation it should be simple:
run gcloud auth application-default login to write ADC to file used by client libraries.
use google-auth-library to get a http client to use to trigger the function.
/**
* TODO(developer): Uncomment these variables before running the sample.
*/
// Example: https://my-cloud-run-service.run.app/books/delete/12345
// const url = 'https://TARGET_HOSTNAME/TARGET_URL';
// Example (Cloud Functions): https://project-region-projectid.cloudfunctions.net/myFunction
const targetAudience = 'https://<REGION>-<PROJECTID>.cloudfunctions.net/<FUNCTIONNAME>';
const { GoogleAuth } = require('google-auth-library');
const auth = new GoogleAuth();
const payload = {"prop1": "prop1Value"};
async function request() {
const client = await auth.getIdTokenClient(targetAudience);
const resp = await client.request({ url: targetAudience, method: 'POST', data: payload });
console.info(`Resp status: ${resp.status}; resp.data: ${resp.data}`);
}
(async () => {
await request();
})();
My understanding was that the google-auth-library would pick up the ADC from the file setup from running gcloud auth application-default login and everything would work.
My user has permission to invoke GCP functions as I can trigger the function using CURL with the header -H "Authorization:bearer $(gcloud auth print-identity-token)" \
However when I run this, it doesn't get past the line:
const client = await auth.getIdTokenClient(targetAudience);
Failing with:
Cannot fetch ID token in this environment, use GCE or set the GOOGLE_APPLICATION_CREDENTIALS environment variable t
o a service account credentials JSON file.
Using PubSub library works fine so expect ADC does work just not sure what am I missing when trying to trigger the GCP function.
Am I using the google-auth-library correctly here ?
Thanks
As mentioned in the thread:
gcloud auth activate-service-account --key-file is only for "you"
running gcloud commands, it won’t be picked up by "applications" that
need GOOGLE_APPLICATION_CREDENTIALS. As you can see from Invoke a
Google Cloud Run from java or How to call Cloud Run from outside
of Cloud Run/GCP?, you either need to have the JSON key file of
Service Account, or have to be running inside a GCE/GKE/Cloud Run/App
Engine/GCF instance.
For this to work on your local environment, I recommend logging in
with gcloud auth application-default login command (this command is
meant to work as if you’ve set GOOGLE_APPLICATION_CREDENTIALS
locally).
If that doesn't work, as a last resort you can refactor your code to
pick up identity token from an environment variable (if set) while
working locally,
such as: $ export ID_TOKEN="$(gcloud auth print-identity-token -q)" $ ./your-app
To know more about how the code does it with a JSON key file,refer to the link and similar implementation there.
For more information you can refer to a similar thread stated as :
Give the default service account access rights to Workspace
resource(s) you're attempting to access.
Use the JSON key file you set
up locally already, to have the Cloud Function run as the same user as
is happening when you run locally.
Essentially do a hybrid where you create a new service account that ONLY has the permissions you want (instead of using the default
service account or your personal user, both of which might have far
more permissions then desired for safety/security), use a key file to
run the Cloud Function under that identity, and only give the desired
permissions to that service account.

Authenticating as a service account outside of the Google Cloud environment using firebase-admin

I'm having trouble on authenticating as a service account in my Next.js app hosted on Vercel. My code is working fine in my dev environment, but it fails with the following error message when I try to run it on Vercel Node.js v14 environment. I guess that it runs on my local machine because I'm logged in gcloud with my email (project owner).
This is the error I'm getting:
Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.
at GoogleAuth.getApplicationDefaultAsync (/var/task/node_modules/google-auth-library/build/src/auth/googleauth.js:173:19)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async GoogleAuth.getClient (/var/task/node_modules/google-auth-library/build/src/auth/googleauth.js:551:17)
at async GrpcClient._getCredentials (/var/task/node_modules/google-gax/build/src/grpc.js:109:24)
at async GrpcClient.createStub (/var/task/node_modules/google-gax/build/src/grpc.js:252:23)
I've created the following service account to use it with my Next.js APIs.
It has all the necessary roles. I've created a JSON key and download it.
I'm using firebase-admin and this is how I'm initializing it:
export const initializeFirebaseAdmin = (): FirebaseAdmin => {
const account = getServiceAccount(); // THIS IS THE SERVICE ACCOUNT JSON KEY (ALREADY PARSED AS AN OBJECT)
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(account),
});
}
return admin;
};
This is what I think it's happening:
From: https://cloud.google.com/docs/authentication/production#automatically
From the image above:
I'm not setting any GOOGLE_APPLICATION_CREDENTIALS environment variable
So I should be on number 2. It will "try to use the service account that is attached to the resource that is running your code."
It's obviously failing and I getting the error
But which resource is it referring to? Which code?
The resource that is running my code is Firebase Admin? Am I not initializing it correctly?
This code should work to authenticate the firebase-admin package:
export const initializeFirebaseAdmin = (): FirebaseAdmin => {
const account = getServiceAccount(); // THIS IS THE SERVICE ACCOUNT JSON KEY (ALREADY PARSED AS AN OBJECT)
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(account),
});
}
return admin;
};
My problem was related to the fact that I was using a client exposed from the firebase-admin package, that doesn't have access to the authentication from the main package. So I needed to pass the credentials to it as well. Like:
const client = new admin.firestore.v1.FirestoreAdminClient({
credentials: SERVICE_ACCOUNT as CredentialBody // <<<<<< THIS IS THE SERVICE ACCOUNT JSON KEY
});
Refer also to: Trying to export backup via admin.firestore.v1.FirestoreAdminClient on a Next.js API route. Error: Could not load the default credentials

Calling `subscribeToTopic` from a locally run cloud function

I keep getting a permission error...
"An error occurred when trying to authenticate to the FCM servers. Make sure the credential used to authenticate this SDK has the proper permissions. See https://firebase.google.com/docs/admin/setup for setup instructions."
...when I locally run a cloud function that calls to subscribeToTopic and unsubscribeToTopic. This problem des not happen when I deploy my function to firebase.
I am using firebase-admin and set up firebase in my cloud function via...
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
The authentication issue only happened when calling subscribeToTopic and unsubscribeToTopic. Writing to the db from the cloud function works fine.
I was able to get around this issue by calling admin.initializeApp w/ a generated service account json file that I created via the firebase console.
Any ideas as to why this happened?

Firebase serve --only functions, admin.auth() locally does not work unless I manually set the key.json

I am trying to run cloud functions locally using firebase serve --only functions. It works if I manually specify the key.json that is downloaded when I create a service account through firebase console here: https://console.firebase.google.com/project/project-id/settings/serviceaccounts/adminsdk.
And then doing export GOOGLE_APPLICATION_CREDENTIALS=key.json. Why do I have to do this? Shouldn't firebase or gcloud handle this correctly? I thought credential in functions.config().firebase was ApplicationDefault. https://developers.google.com/identity/protocols/application-default-credentials. According to that, the env variable like above is checked first, so it works if that is there. But shouldn't #2 validate me correctly?
I have gcloud for app engine so I have done gcloud init and gcloud auth application-default login.
I initialize my app in index.js
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
firebase serve --only functions deploys the functions fine. But when I execute one which uses this code admin.auth().getUser(uid)... it breaks with the following error:
Error: An internal error has occurred. Raw server response: "{"error":{"errors":[{"domain":"usageLimits","reason":"accessNotConfigured","message":"Access Not Configured. Google Identity Toolkit API has not been used in project 7640...50 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/identitytoolkit.googleapis.com/overview?project=7640...50 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.","extendedHelp":"https://console.developers.google.com/apis/api/identitytoolkit.googleapis.com/overview?project=7640...50"}],"code":403,"message":"Access Not Configured. Google Identity Toolkit API has not been used in project 7640...50 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/identitytoolkit.googleapis.com/overview?project=7640...50 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry."}}
The project number doesn't correspond to my firebase project or project id/number in console.cloud.google.com
Without gcloud, I get this error
Error: Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: "Error fetching access token: invalid_grant (Token has been expired or revoked.)". There are two likely causes: (1) your server time is not properly synced or (2) your certificate key file has been revoked. To solve (1), re-sync the time on your server. To solve (2), make sure the key ID for your key file is still present at https://console.firebase.google.com/iam-admin/serviceaccounts/project. If not, generate a new key file at https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk
similar to https://github.com/urish/firebase-server/issues/81
I already have a key file generated. I've tried deleting my existing ones and making new ones, the only solution is to download this key that is generated and manually export the environment variable
Is there any other way than that?
Technically yes, but you can build your code so that it uses a local key when testing locally, and uses the service key when running on the server.
For this I set the env var FB_SERVICE_ACCOUNT_KEY to the path of the json secret. When it is unset (i.e. deployed within Firebase functions), the standard init will be used.
Here is a basic example
if(process.env.FB_SERVICE_ACCOUNT_KEY){
var serviceAccount = require(process.env.FB_SERVICE_ACCOUNT_KEY);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`
});
}
else{
admin.initializeApp(functions.config().firebase);
}

Firebase function signInWithCustomToken undefined with Node.js

So I've created a custom token as such
const token = authRef.createCustomToken(options.userId, {
app: appRootName,
debug: !!options.debug
});
And then attempt to sign it in as such
return authRef.signInWithCustomToken(token).then((user) => {
return user;
});
Custom creation token succeeds, but signInWithCustomToken spits out this error.
TypeError: authRef.signInWithCustomToken is not a function
This is Firebase 3 for nodejs and the service account has been set up. The app was properly initialized (or token generation wouldn't work in the first place), what's the issue here?
Actual issue is that
authWithCustomToken
exists in firebase and not in firebase-admin which is mostly used when using firebase security features.
I got the same error, hope the following code can solve your problem.
Be careful using apiKey or serverKey
var firebase = require('firebase')
firebase.initializeApp({
apiKey: '*******',
databaseURL: 'https://*****.firebaseio.com'
})
var customToken = '*********'
firebase.auth().signInWithCustomToken(customToken).catch(function(error) {
var errorMessage = error.message
console.log(errorMessage)
})
I have the same issue when deployed using a CI/CD pipeline. Worked fine when directly deployed to firebase from my local. Both approaches uses same token to deploy. I am using firebase-admin latest version, i.e., 5.11.0.
The only solution I found is to
Set the keys to cloud functions config, e.g., firebase functions:config:set custom.apikey=KEY_HERE,
Read the keys inside the cloud function that renders the page and set to the firebase config retrieved via functions.config().firebase,
BTW, we are rendering the pages via nextjs which is hosted in a google cloud function. This rendered page communicates with the firebase firestore database.

Resources