Isn't it a bad idea to store your service account key in the code? Firestore documentation is saying I should [duplicate] - firebase

This question already has answers here:
Is it safe to expose Firebase apiKey to the public?
(10 answers)
Closed 1 year ago.
It is very likely the case I am misunderstanding how all of this works as I am still a newer programmer, but in every course I have taken I have been told not to expose any credentials within the code.
In this Firestore documentation, it tells you to store your service account's credentials as a JSON file and include the file in the directory for the SDK to access. Am I wrong in thinking this is a security issue?
Firestore Getting Started Documentation
Under Initializing Firestore
To use the Firebase Admin SDK on your own server (or any other Node.js environment), use a service account. Go to IAM & admin > Service accounts in the Cloud Platform Console. Generate a new private key and save the JSON file. Then use the file to initialize the SDK:
const serviceAccount = require('./path/to/serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
const db = admin.firestore();
Am I missing something here? Why is it okay to do this?

In fact it depends how we interpret "not to expose any credentials within the code."
Firstly, a main important rule, is to never include secrets (password, or service account keys, or any other confidential data) into the source code, and especially in source code configuration (git / github).
Secondly, in some situation, the only solution to authenticate to a service or API is to use a service account key. In this case, we must keep this file separated from source code, and provide it to app by an environment variable pointing to it.
If your code is running on Google Cloud Platform (App Engine, Cloud Functions, Cloud Run, Firebase Functions...), you can use default authentication provided directly by GCP, and avoid any service account key.
Check Firebase documentation.
In this case, you keep it just for development purpose on your local machine.

Related

Why we pass apikey,databaseurl,storageBucket,Domain, appId, messagingSenderId in Firebase Nodejs Project

When initializing Firebase in Nodejs project, why we include apikey,databaseurl,storageBucket,authDomain, appId, messagingSenderId, projectId in Firebase.initializeApp()? Without some of these properties, it is still working, then why do we need to pass it?
And also for security, we use Firebase Authentication for checking user auth.uid, so why do we need to pass the above properties in initializeApp()?
Does passing these properties, Firebase will check it by default? If Firebase checks it by default, we don't need Firebase Authentication then?
I'm a beginner kindly help.
Firebase consists of >18 products these days, and many of them take different configuration data at startup. But since you only call initializeApp once for all these products, you have to pass the configuration data for all products in this one call.
That's why all example in the Firebase documentation and console show how to pass all possible configuration data. Depending on the products you actually use, and the platform you run on, you may need fewer of these values, but including all of them never causes problems.
When you register an app with a Firebase project, the Firebase console provides a Firebase configuration file (Apple/Android apps) or a configuration object (web apps) that you add directly to your local app directory.
That is, a Firebase config file / object associates an app with a specific Firebase project and its resources. It consists of unique and non-secret identifiers for your project. A Firebase config file generally consists of apiKey, databaseURL, projectId, storageBucket, messagingSenderId, appId, measurementId.
These parameters are required by Firebase and Google services to communicate with Firebase server APIs and to associate client data with the Firebase project and Firebase app. The apiKey and the projectId are the mandatory fields in the configuration file/object. And, other fields are optional. Each of the other fields corresponds to an optional part of Firebase.
This is because Firebase contains many services/products such as realtime nosql database services, blob storage, push notifications/messaging, and ofcourse Authentication among many more things.
If you do not desire to use the other parts of Firebase, simply do not reference them nor enable them. It is completely fine to only use Firebase Authentication.
The content of the Firebase config file or object is considered public, including the app's platform-specific ID (Apple bundle ID or Android package name) and the Firebase project-specific values, like the API Key, project ID, Realtime Database URL, and Cloud Storage bucket name. Given this, it is recommended to use Firebase Security Rules to protect your data and files in Realtime Database, Cloud Firestore, and Cloud Storage.

Accessing firestore from self-hosted server

I need to be able to access (read, write) firestore from my self-hosted server (not on gcloud).
I also need to reserve write permissions to the app hosted on my self-hosted server, and allow noone else to be able to write to the server, so the security rules for write applications are denied by default.
According to this document:
https://firebase.google.com/docs/firestore/client/libraries#server_client_libraries
using a service account should allow me to be able to access all firebase products, including firestore, without any restrictions.
But, as soon as I restrict the security rules, I am unable to write to firestore from my service, instantiated with service account credentials following the steps in this documentation:
https://firebase.google.com/docs/admin/setup#initialize-sdk
I get the following error:
PERMISSION_DENIED: Missing or insufficient permissions.
What do I have to do to allow my application to write to firestore from my own servers?
Update:
I am using java for my backend applications.
Servers (or cloud functions) are secure environment which can be accessed only by you and people you have authorized. You don't actually use the Firebase Client SDKs in a server. Instead you use the Firebase Admin SDK. It uses a service account and has privileged access to your Firebase Project.
What does privileged access mean?
The documentation has explained it all but long story short it has complete access like you to the project. It does not follow any security rules.
If you are implementing the Admin SDK which will deal with API requests coming from your clients then make sure you authenticate them. To do so you would ideally pass the Firebase ID Token from your frontend to the API and verify
it using the Admin SDK which returns a decoded token object containing UID, custom claims and some metadata about that user.
I'm not sure which language you use but here's a simple getting started for NodeJS:
// Install the Admin SDK
npm install firebase-admin
// index.js
const admin = require("firebase-admin")
const path = require("path");
admin.initializeApp({
credential: admin.credential.cert("path/to/key")),
databaseURL: "https://<project-id>.firebaseio.com/"
})
const firestore = admin.firestore()
const auth = admin.auth()
// firebase.firestore() and firebase.auth() in client SDKs
Rest of the syntax is mostly the same as Client SDK.
You can create a service account from the project settings.
The issue that I was facing has already been reported here:
https://github.com/googleapis/java-firestore/issues/431
The fix was an odd one, and cannot be explained without going deep into the SDK and how it works, which I did not have time to check:

Google App Engine from project A cannot use Firebase Cloud Messaging from project B

We are running 2 separate Google Cloud Projects, Project A and Project B.
Project A is currently running an App Engine (AE) based API.
The API on project A needs to access the Firebase features on project B.
✅ I have setup cross projects access between the two projects using IAM and most functions are working fine.
App Engine on project A is able to access the Firebase real time db (RTDB) and Firebase Auth functions on Project B.
But we are unable to connect to Firebase Cloud Messaging on Project B
As far as I can see tracing through the Firebase admin SDK calls, we are able to obtain access tokens, and they're all valid as we can use some of the services (i.e. RTDB, Auth)
However, when we try using Firebase Cloud messaging we receive the following 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.
Which seems to originate from a 401 unauthorized response to this service https://iid.googleapis.com/iid/v1:batchAdd
It doesn't seem to matter what amount of Roles on Project B I throw at the Project A service account.
If I use a separate Firebase service account that originated from Project B and use that json file as a credential then the issue go away, but I don't want to distribute credential files around, and everything else work fine without, except FCM.
FWIW this is how i initialise the firebase admin sdk
const firebaseAdmin = require('firebase-admin');
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.applicationDefault(),
projectId:'project-b',
databaseURL: "https://project-b.firebaseio.com",
});
I've also tried setting the serviceAccountId option to the project B service account email that works when manually loaded, but that doesn't change anything (also added Service Account Token Creator role)
The services account has also been given all the permissions listed here https://firebase.google.com/docs/projects/iam/permissions#messaging
Feels like there's a simple connection missing (either a role, or API setting) but I've ran out of idea.
Has anyone done this before and can provide some guidance?
EDIT [CORRECTED IN EDIT 3] I've also tried to manually create a service account on Project B with the same working roles as the Firebase service account that works and i'm getting the same issue (even though it was created on Project B). So sounds like maybe it's not a role issue, nor a cross-project issue?
EDIT 2 I'm on Node Js with latest firebase admin sdk
EDIT 3 Ignore the first edit, i hadn't configured the custom service account correctly, works fine with a service account created from scratch in Project B. So back to cross-project issue then I guess..
So in the case of Real time database, you only need to give Firebase Realtime Database Admin role to the Project A's App Engine service account on Project B.
In Firebase Cloud Messaging you can give Firebase Cloud Messaging Admin role on Project B AND the cloudmessaging.messages.create permission to the service account on Project A.
This will go around the initial 401 unauthorised response from messaging calls.
However, this will still result in an error, this time NOT FOUND, which indicates that the subscription token was not found.
It seems that calls to FCM don't use the projectId setting in firebase admin initialisation and always execute against the project the service account was created in (Project A in this case).
I haven't been able to find a way for FCM calls to executed in the specified projectId scope from another project.

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

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.

firebase `signInWithEmailAndPassword` undefined

I'm a little new to firebase/cloud functions so forgive if I'm missing something simple.
I'm frustrated now because what I see on the documentation is not working. I read this part of the documentation: signInWithEmailAndPassword but when I use it in my nodejs code that I deploy to Firebase Cloud functions I get the error:
TypeError: auth.signInWithEmailAndPassword is not a function
Note that auth is actually auth(), I just decided to store the result of auth() in the variable auth, so don't let it confuse you.
I looked at the documentation and I don't see anything wrong I'm doing. This post Firebase Auth On The Server With Node.JS / Express and got this for it
If you initialize the Node.js SDK with an API key instead of a service account, you can use the normal client-side auth methods like authWithEmailAndPassword()
Currently I initialize it like this:
admin.initializeApp(functions.config().firebase)
What exactly am I expected to change so that the signInWithEmailAndPassword method becomes available on the object?
Do I really need to get a API key and initialize it like I'm initializing a frontend project when I'm using firebase-admin on a nodejs server?
Cloud Functions run on a trusted environment, and is using the Firebase Admin SDK to access Firebase services. There is no way (nor usually a need) to sign in as a user with email+password using the Admin SDK.
The Firebase Admin SDK runs with administrative privileges, giving it already has full access to the entire Firebase project that the code runs in.

Resources