How can I "admin.initializeApp();" no arguments in local - firebase

I am always grateful for your help.
I want to write code admin.initializeApp(); both locally and in production.
When I deploy functions to production with no auguments, it works.
But locally, it requires me to write it like below:
const serviceAccount = require("/home/yhirochick/development/ServiceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://xxxx.firebaseio.com/"
});
In the official documentation it says that configuration is applied automatically when you initialize the Firebase Admin SDK with no arguments
But when I execute the command firebase serve --only functions locally and some calls some requests by postman produce the error below:
[2019-07-22T06:45:26.227Z] #firebase/database: FIREBASE WARNING: Provided
authentication credentials for the app named "[DEFAULT]" are invalid. This
usually indicates your app was not initialized correctly. Make sure the
"credential" property provided to initializeApp() is authorized to access the
specified "databaseURL" and is from the correct project.
I want to know How can I "admin.initializeApp();" no arguments locally.

I have grappled with this also and I don't think the local testing scenario currently is explained very well in the official documentation. But here is a solution:
For your local environment you need to download the firebase projects firebase service account json file (found in firebase console under project settings -> service account) and set an environment variable GOOGLE_APPLICATION_CREDENTIALS to point to the file:
# Linux/MACOS version
export GOOGLE_APPLICATION_CREDENTIALS="[PATH_TO_YOUR_SERVICE_ACCOUNT_FILE]"
Read more here, also on how to do this on Windows
Now you will be able to use admin.initializeApp() (with no arguments) locally.
A possible downside of this approach is that you have to set the environment variable each time you fire up a terminal before you start the firebase emulator, because the variable gets deleted when you end the session.
Automate it...
You could automate the export ... command by bundling it together with the command that fires up the emulator. You could do this by adding an entry to the scripts section of your package.json, e.g.:
"local": "export GOOGLE_APPLICATION_CREDENTIALS='[PATH_TO_YOUR_SERVICE_ACCOUNT_FILE]' && firebase emulators:start --only functions"
Then, in this example, you would only need to type npm run local.
Alternative: provide explicit credentials in local environment only
Look at this example: https://stackoverflow.com/a/47517466/1269280.
It basically use a runtime node environment variable to separate between local and production and then use the explicit way of providing credentials in the local environment only.
This is my preferred way of doing things, as I think it is more portable. It allows me to put the service account file inside my codebase and not deal with its absolute file path.
If you do something like this then remember to to exclude the service account file from your repo! (it contains sensitive info).
Background: difference between production and local service account discovery
The reason that admin.initializeApp() (with no arguments) works out-of-the-box in production is that when you deploy to production, i.e. Firebase Functions, the code ends up in a 'Google managed environment'. In Google managed environments like Cloud Functions, Cloud Run, App Engine.. etc, the admin SDK has access to your applications default service account (the one you downloaded above) and will use that when no credentials are specified.
This is part of Google Clouds Application Default Credentials (ADC) strategy which also applies to firebase functions.
Now, your local environment is not a 'google managed environment' so it doesn't have access to the default service account credentials. To google cloud, your local box is just an external server trying to access your private Firebase ressources. So you need to provide your service account credentials in one of the ways described above.
Before I knew this, I thought that because I was already logged in to firebase via my terminal i.e. firebase login and were able to deploy code to firebase, the firebase emulator would also have the necessary credentials for the firebase admin sdk, but this is not the case.

Related

GOOGLE_APPLICATION_CREDENTIALS for dummy Firebase project

I'm learning to set up Firebase Emulators correctly to work on my projects and I came up with a problem. I can setup the emulators and make them work locally, however, when trying to access firestore it seems to try to access the real Firestore Instance instead of the emulator.
Right now I'm initializing the app like this (in Cloud Functions)
admin.initializeApp();
const db = admin.firestore();
But when I'm running a function I'm getting:
Failed to initialize and load triggers. This shouldn't happen: Failed to read credentials from file GOOGLE_APPLICATION_CREDENTIALS.json: Error: ENOENT: no such file or directory, open 'GOOGLE_APPLICATION_CREDENTIALS.json'
The thing is that if I use the credentials I generated for my project it will work with the real Firestore instance instead of the emulator.
How should I make credentials for my emulated services?
If you are using Firebase Functions emulator as well then Admin SDK will connect to all the running emulators e.g. if only Auth emulator is running then it'll use the emulator and connect to production for other services like Firestore. You can explicitly set the FIRESTORE_EMULATOR_HOST environment variable and Admin SDKs will use the emulator then.
Checkout the documentation for more information.

G-cloud sdk --> problems about firebase projects and permissions

I'm developing a backend microservice app with node.js along with #google-cloud/firestore and i'm trying to access my firebase project locally with gcloud-sdk. I've run gcloud init so i can log in and chose the project i need to connect to in order to access the db.
The problem is that, i keep getting 7 PERMISSION_DENIED: Missing or insufficient permissions when i try to make any type of interaction with the db even though i already have all permissions in the project. At the beginning i thought that maybe i was having that problem because i was using a different account that did not have permissions to access these projects, but that didn't make sense because i do not see that account in my list of accounts in the sdk
But, when i gave permissions to the other account that i thought maybe logged in in my sdk, i could access the db, so it was weird, it is like it's stuck with the other account that maybe i added when i first install the sdk.
So, i've tried everything to correct this, i've deleted all accounts from my gcloud sdk, i've deleted the list of configurations, i've uninstalled (twice) the sdk and installed it again (since people that i work with told me that they did this and worked for them, since they had same issue), i've run g cloud init to log in again and all that stuff, and still, when i try to connect to my firebase db, it still says that i still do not have permissions, it's like the account that i'm logging in is not the one that is being saved/used to access my project.
What can i do to make this work ?
EDIT #1 -> How i'm connecting to firebase and sdk commands to connect to the project
Connecting to my project on firestore
import 'reflect-metadata';
import { Firestore } from '#google-cloud/firestore';
import { GCP_PROJECT } from '#util';
export const firestore = new Firestore({ projectId: GCP_PROJECT });
Commands to connect to my project by sdk
-> gcloud init
Welcome! This command will take you through the configuration of gcloud.
Settings from your current configuration [coordinadora-work] are:
core:
account: diego.cifuentes#coordinadora.com
disable_usage_reporting: 'True'
project: cm-reparto-dev
Pick configuration to use:
[1] Re-initialize this configuration [coordinadora-work] with new settings
[2] Create a new configuration
Please enter your numeric choice: 1
Your current configuration has been set to: [coordinadora-work]
You can skip diagnostics next time by using the following flag:
gcloud init --skip-diagnostics
Network diagnostic detects and fixes local network connection issues.
Checking network connection...done.
Reachability Check passed.
Network diagnostic passed (1/1 checks passed).
Choose the account you would like to use to perform operations for this configuration:
[1] diego.cifuentes#coordinadora.com
[2] Log in with a new account
-> Please enter your numeric choice: 1
You are logged in as: [...my account that is having problems...].
Pick cloud project to use:
...
-> Please enter numeric choice or text value (must exactly match list item): 21
Your current project has been set to: [...project that i'm having problems with...].
-> Do you want to configure a default Compute Region and Zone? (Y/n)? n
Your Google Cloud SDK is configured and ready to use!
The Google Cloud Client libraries use the Application Default Credentials, not the current credentials setup using gcloud auth login.
To setup your Application Default Credentials for local development, simple execute gcloud auth application-default login. This will open a browser window and allow you to select the account to use as default credentials.
Another method is setting the environment variable GOOGLE_APPLICATION_CREDENTIALS to point to a service account key file.
Read more here : https://cloud.google.com/docs/authentication/application-default-credentials
When running in GCP, your credentials will get picked up from the environment, i.e. the service account your Cloud Function is running with.
https://cloud.google.com/docs/authentication/client-libraries

Does Firebase Admin SDK deployed at Cloud Run require GOOGLE_CLOUD_PROJECT env var?

We've deployed a service to Cloud Run which uses Firebase Admin SDK. This service uses multiple auth-related methods. We've found out that if we do not include the GOOGLE_CLOUD_PROJECT env var, the method setCustomUserClaims(...) throws following error: Failed to determine project ID for Auth. Initialize the SDK with service account credentials or set project ID as an app option. Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.
However, we've also checked that if the env var is not present, other methods, such as createCustomToken(...) work just fine. How is this possible? Should we use GOOGLE_CLOUD_PROJECT or not?
createCustomToken mints and signs its authentication tokens within the SDK.
This is in contrast to setCustomUserClaims that has to make network calls to do its job, mainly to the endpoint:
https://identitytoolkit.googleapis.com/{version}/projects/{projectId}/accounts:update
As this endpoint uses the Project ID, it needs to be provided from somewhere.
As of the time of writing, it looks for it in these locations, in the following order:
options.projectId from initializeApp() (this is normally filled in by process.env.FIREBASE_CONFIG)
options.credential.projectId from initializeApp(), if the credential is a ServiceAccountCredential
process.env.GOOGLE_CLOUD_PROJECT
process.env.GCLOUD_PROJECT
options.credential.getProjectId() from initializeApp(), if the credential is a ComputeEngineCredential

How Can I Obtain GCP service account credentials on Google Cloud Run?

This page explains both:
Obtaining and providing service account credentials manually for developing local, deploying on-premises, or deploying to another public cloud.
Obtaining credentials on Compute Engine, Kubernetes Engine, App Engine flexible environment, and Cloud Functions
But there is no mention of obtaining credentials on Cloud Run. I'd appreciate it if you give instructions for obtaining credentials and setting firebase-admin initializeApp and firebase initializeApp for authentication on Cloud Run.
The documentation suggests that you can use the default service account just like other Google Cloud products as described here. The Firebase Admin SDK should use that account when initialized with no parameters.
There are also steps described if you want to use a non-default service account, which you can simply configure in the console or provide with gcloud.
If you must provide a file that's readable at runtime, you will have to deploy an image with that file added to the image. There is no short set of steps to add that file - you will have to make your docker build include it in a readable location, and your code will know where to look for it in order to load it.

How to get the firebase functions URL in a firebase hosted webapp?

I am working on a project in which we have pre-existing cloud functions in use with Firebase. We are adding a small React SPA using firebase cloud hosting, and this SPA will interact with some of the existing public cloud functions.
The way we have been doing things so far, we have a dev project, and a production project in Firebase. For cloud functions, this works fine, we have environment specific config set up with firebase functions:config:set for differentiations between prod and dev servers.
The problem comes with the hosted SPA contacting the cloud functions. I've seen a lot of questions on how to access the environment config in the hosted code, eg this one: How to reference Firebase Functions config variables from a Firebase-hosted application? where the answer seems to be to have firebase functions that return the values of the environment variables, but for me this just moves the problem further back on step. I fully understand that having the environment variables accessible to this code would be a massive security problem as the SPA is run in the browser.
The only environment specific config I really need for the hosted SPA is the base address for the cloud functions.
eg if in my cloud functions I have
const functions = require('firebase-functions');
const express = require('express');
const test = express();
test.on('/hello/:target', (req, res) => {
res.send(`Hello ${req.params.target}`);
})
exports.test = functions.https.onRequest(test);
then having deployed, this cloud function is available both at https://us-central1-DEV-PROJECT-NAME.cloudfunctions.net/test/hello/world and https://us-central1-PROD-PROJECT-NAME.cloudfunctions.net/test/hello/world . How would I best get the appropriate root url (https://us-central1-DEV-PROJECT-NAME.cloudfunctions.net or https://us-central1-PROD-PROJECT-NAME.cloudfunctions.net) for the project that the SPA is deployed to?
eg. is there some global I can access in the frontend js code where I could do something like:
const url = `${__FIREBASE_GLOBALS__.cloudFunctions.baseUrl}/test/hello/${input}`;
And have the url be correctly defined based on which project the hosted app is deployed to?
I'm assuming here that you're not using Firebase in any other way in your SPA other than to call Cloud Functions (since you didn't say otherwise).
Read the Firebase web setup docs for Firebase Hosting, especially the section on SDK imports and implicit initialization. When you host a site with Firebase Hosting, there are some special URLs that give you the configurations for that project. There are some special script includes that give you access to Firebase products. In particular, note the relative path URI /__/firebase/init.js will yield JavaScript that initializes the Firebase JavaScript SDK with the default settings for your project. Go ahead and access that in a browser pointing to your web app. You're probably interested in the projectId property of the config.
If you want to get a hold of that value, you can use the Firebase SDK, which would be initialized by the script includes from the first link above. Minimally, you could add:
<script src="/__/firebase/5.8.2/firebase-app.js"></script>
<script src="/__/firebase/init.js"></script>
Then later on (see API docs):
firebase.app().options.projectId
to get the ID of the project where Firebase Hosting is serving the content. You can use that to build the URL to your functions.
It might also be convenient for you to port your HTTP functions to callable functions and invoke them from the web site with the Firebase SDK to invoke kthem. Or not.
I was able to get the region and appId from the environment variables.
eg:
console.log(process.env);
Check your firebase logs
{ ...
ENTRY_POINT: 'server',
X_GOOGLE_FUNCTION_TRIGGER_TYPE: 'HTTP_TRIGGER',
FIREBASE_CONFIG: '{"projectId":"pid","databaseURL":"https://pid.firebaseio.com","storageBucket":"pid.appspot.com","locationId":"europe-west"}',
X_GOOGLE_FUNCTION_NAME: 'server',
FUNCTION_TRIGGER_TYPE: 'HTTP_TRIGGER',
X_GOOGLE_GCLOUD_PROJECT: 'pid',
FUNCTION_NAME: 'server',
X_GOOGLE_GCP_PROJECT: 'pid',
X_GOOGLE_FUNCTION_REGION: 'us-central1',
FUNCTION_REGION: 'us-central1',
X_GOOGLE_ENTRY_POINT: 'server',
GCLOUD_PROJECT: 'pid',
GCP_PROJECT: 'pid',
... ommited
}
Out of these GCP_PROJECT, GCLOUD_PROJECT, FUNCTION_REGION, FUNCTION_NAME should work. So for eg. process.env.FUNCTION_REGION
Not sure how reliable this will be.

Resources