How do I authenticate with Firebase Auth without any json files? - node.js

I am working on a system and trying to implement authentication with Firebase Auth.
I see that Google generates private keys on json files. And it works fine authenticating with them according to this tutorial
Basically setting the GOOGLE_APPLICATION_CREDENTIALS environment variable to the path where the keys are contained.
Or sending the data manually like this:
var admin = require("firebase-admin");
var serviceAccount = require("path/to/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://my-firebase-project.firebaseio.com"
});
However I don't want to hold any physical files. Our entire stack is automated (aside from certain keys that are set as ENV vars from an external parameter store), and as such everything sits in source control, but I don't want to upload a key file to source control.
How do I authenticate with Firebase Auth without the use of any physical files?

There's no reason you couldn't set keys with environment variables.
Use process.env.VAR_NAME to get environment variables from within your Node.js application.

Related

Firebase Functions Service Accounts with Two Projects

I have two firebase projects, one for production and one for development. I've created service account keys for both projects and I initialize admin in my functions folder like so:
const admin = require("firebase-admin");
const serviceAccount = require("./service-account.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
storageBucket: "some-app.appspot.com",
});
const db = admin.firestore();
module.exports = {
db,
admin,
};
But I need firebase to use the appropriate service account for each project. My app is setup to use the production project when deployed in production, but the way I've set up admin.js (above) it always takes the development service-account credentials.
How can I set the service account credentials as an environment variable? Is there a way to add the key to firebase config in the console? Or should I just hard code in the production service account and live with that?
I found this in the docs, but it doesn't appear to solve my problem.
serviceAccount = require('./serviceAccount.json');
const adminConfig = JSON.parse(process.env.FIREBASE_CONFIG);
adminConfig.credential = admin.credential.cert(serviceAccount);
admin.initializeApp(adminConfig);
I appreciate any help you can provide.
You could specify the configuration to use in an environment variable as shown here and then pick that up in your code.
But that should not be necessary in most cases, since calling admin.initializeApp() without parameters already initializes the Admin SDK with the administrative credentials for the current project.

Using firebase-admin. What are service account credentials used for?

I'm building a cloud function to get some documents and generate an HTML response.
And right now what I have is:
myFunction.js
import * as admin from 'firebase-admin';
import serviceAccount from './someServiceAccountKey.json';
// OPTION #1 <-----
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://myfirebaseproject.firebaseio.com"
});
// OPTION #2 <------
admin.initializeApp();
// FUNCTION <------
async function(req,res) {
const docSnapshot = await admin.firestore().collection('someCollection').doc('someDoc').get();
// AND SO ON...
}
At some point I've created that serviceAccount key (I don't remember from which tutorial) and I've been using on my functions to "grant access" to the firebase-admin (as in OPTION #1 from the code above). Because I thought I needed to.
But I've found out that even without any initializing parameter or serviceAccount credential (as in OPTION #2 from the code above), I can initialize the firebase-admin and access my firebase project resources (like Firestore) without any issues.
And I can do this on deployed functions, which makes sense, since they are inside the Firebase project's cloud environment, and if they were deployed, they should have access to the project's resources, without the need for a service account, right?
But I also found out that I can do this in my Node.js local environment. By calling the function with npx babel-node myFunction.js (using babel node to transpile and execute). The function is able to access and query my Firestore (on-line data, this is not the Firestore emulator) without any issues.
Where is my local environment getting permission to access my project's resources from? Is it from the firebase-tools that is installed and logged in my project?
If I don't need them in both cases. What are service accounts mainly used for?
You initialize SDK in different way depending on the environment:
Cloud Functions
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
let db = admin.firestore();
Google Cloud Platform
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.applicationDefault()
});
const db = admin.firestore();
// ...
Node server
const admin = require('firebase-admin');
let serviceAccount = require('path/to/serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
let db = admin.firestore();
Look here for more details
So, your observations are correct.
However, since even using OPTION #2 you are getting access to your firebase, then most likely you need to check your database rules and make sure you are prohibiting unauthenticated access to your db.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
More about the rules here.
Firebase data is secured using rules, before you set up any rules, any user (even unauthenticated) can access your data.
https://firebase.google.com/docs/rules/basics
Once you have rules set up, then either the user or the server will have to authenticate in order to read/write data.
Users authenticate by logging on with google (or whatever client side security you have ocnfigured).
Servers authenticate by using the service account. Service accounts default to having admin access to the database.
https://firebase.google.com/docs/database/admin/start#authenticate-with-admin-privileges

How to keep google-cloud-auth.json securely in app.yaml as an environmental variable?

I'm new to deployment/securing keys, and I'm not sure how to securely store the google-cloud-auth.json (auth required for creating the API client) outside of source code to prevent leaking credentials.
I've currently secured my API keys and tokens in my app.yaml file specifying them as environmental variables which successfully work as expected and shown below.
accessruntime: nodejs10
env_variables:
SECRET_TOKEN: "example"
SECRET_TOKEN2: "example2"
However my google-cloud-auth.json is kept as its own file since the parameter used for creating the client requires a path string.
const {BigQuery} = require('#google-cloud/bigquery');
...
const file = "./google-cloud-auth.json";
// Creates a BigQuery client
const bigquery = new BigQuery({
projectId: projectId,
datasetId: datasetId,
tableId: tableId,
keyFilename: file
});
According to the Setting Up Authentication for Server to Server Production Applications:
GCP client libraries will make use of the ADC (Application Default Credentials) to find the credentials meant to be used by the app.
What ADC does is basically to check if the GOOGLE_APPLICATION_CREDENTIALS env variable is set with the path to a service account file.
In case the env variable is not set, ADC will use the default service account provided by App Engine.
With this information I can suggest a couple of solutions to provide these credentials safely:
If you require to use a specific service account, set the path to the file with the GOOGLE_APPLICATION_CREDENTIALS. This section explains how to do that.
If you are not a fan of moving credential files around, I would suggest trying to use the default service account provided by the App Engine.
I just created a new project and deployed a basic app by mixing these 2 guides:
BigQuery Client Libraries
Quickstart for Node.js in the App Engine Standard Environment
My app.yaml had nothing more than the runtime: nodejs10 line, and I was still able to query through the BigQuery client library, using the default service account.
This account comes with the Project/Editor role and you can add any additional roles you need.

Firebase: How to verify Google login token id from Android & iOS

I want to verify on my node backend all the tokens (Google login) i get from my Android app. I started with initializing the firebase module like this:
var admin = require('firebase-admin');
var serviceAccount = require('googlefirebase');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: url
});
This gave me some error about some DEFAULT name and i found out that i needed to use this code:
var admin = require('firebase-admin');
var serviceAccount = require('googlefirebase');
admin.initializeApp(functions.config().firebase);
Then i realize that i need to install and init the project on my server so i did this:
firebase login
firebase init
firebase use --add project
firebase functions:config:set "google-services.json" (just the project_info data of the json that i downloaded from firebase)
Now i get this error:
Error: Must initialize app with a cert credential or set your Firebase
project ID as the GOOGLE_CLOUD_PROJECT environment variable to call
verifyIdToken()
EDIT START
I get this error when i call:
admin.auth().verifyIdToken(token).then(function(decodedToken) {}.catch(){};
EDIT END
I already "init" firebase (or at least i thing so) and created the environment variables: GOOGLE_CLOUD_PROJECT and FIREBASE_CONFIG and i keep getting the same error.
So, whats the right way to get firebase to work? what am i missing? is verifyIdToken the right method to verify the token? i just want to verify the google login token.
With Cloud Functions for Firebase, you're not supposed to initialize like this any more:
admin.initializeApp(functions.config().firebase);
You're supposed to use no arguments:
admin.initializeApp();
Also, your google-services.json file is not useful in Cloud Functions. That's only for use in an Android app.
you should have a FIREBASE_CONFIG environments variable and then call
// Initialize the default app
var admin = require('firebase-admin');
var app = admin.initializeApp();
that config variable contain the initialize information like :
databaseURL: 'https://databaseName.firebaseio.com',
storageBucket: 'projectId.appspot.com',
projectId: 'projectId'

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);
}

Resources