initializeApp when adding firebase to app and to server - node.js

I'm using firebase for auth and db, and AWS lambda for cloud functions.
To add firebase to my JS project, I initializeApp with the firebase config as parameter, as documented here : https://firebase.google.com/docs/web/setup.
As documented here : https://firebase.google.com/docs/admin/setup, I also need to initializeApp in my lambda function.
Something as follows here :
const admin = require('firebase-admin');
const serviceAccount = require('../path/to/service-account.json');
const firebaseAdmin = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "dB_URL"
});
The credentials come from the firebase-admin library, so I cannot add this to my web firebase config. So, I need to initialize twice.
However, if I proceed like this, the server will throw an error :
The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once.But if you do want to initialize multiple apps, pass a second argument to initializeApp() to give each app a unique name.
Am I missing something here ? What's the best practice ? I'm confused.
Someone faced the same issue before it seems : Use Firebase SDK with Netlify Lambda Functions
What worked for this user was to use the REST API as documented here : https://firebase.google.com/docs/projects/api/workflow_set-up-and-manage-project
The documentation says it's in beta though.
Thanks for your kind help

It seems that lambda may load the script file that calls admin.initializeApp({...}) multiple times. To prevent the Admin SDK from initializing multiple times, you can for example detect if that already happened:
if (!admin.apps.length) {
const firebaseAdmin = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "dB_URL"
});
}

Have similar problem. Based on answer from #Frank van Puffelen ( Thanks to him ), Now a days, Using ES6 ( Firebase admin V >= 9 )
import { initializeApp, applicationDefault, getApps } from "firebase-admin/app";
...
if ( !getApps().length ) {
initializeApp({
credential: applicationDefault(),
databaseURL: 'https://XXXXXXX.firebaseio.com'
});
}
...

An alternative in the simplest case might be something like this:
import { initializeApp, getApp } from "firebase-admin/app";
function appSingleton () {
try {
return getApp()
} catch (err) {
return initializeApp()
}
}

try removing const firebaseAdmin = ... since you can already use admin to call firebase services. Just use
admin.initializeApp(
{
credential: admin.credential.cert(serviceAccount),
databaseURL: "dB_URL"
});
Then, you are good to go.

Related

how to upload firebase images to web using local emulator?

I'm trying to upload some images to a firebase storage bucket,
and trying to run the code locally in the firebase emulator.
With most examples I'll get an error like:
"Unhandled error FirebaseError: Firebase: Need to provide options, when not being deployed to hosting via source.
(app/no-options). at initializeApp
I tried this: https://firebase.google.com/docs/admin/setup
const defaultApp = initializeApp({ credential: applicationDefault() })
Which may or not be working.
I think this creates an authed app (maybe?) but if I later use
const storage = getStorage() I get the same auth error.
So next, I tried using the defaultApp for next steps, like:
const storage = defaultApp.storage();
that doesn't error, but it returns something different from getStorage, that I can't seem to use for other needed things like getting a ref
as per examples https://firebase.google.com/docs/storage/web/upload-files
So it's not clear how to upload files to firebase from the local simulator.
my full function looks like this:
import cuid from "cuid";
import {
getStorage,
ref,
uploadBytes,
uploadBytesResumable,
getDownloadURL
} from "firebase/storage";
import { admin, defaultApp } from '../utils/firebaseInit'
export function saveToBucket(url: string): string {
const normalStorage = getStorage() // Gets a FirebaseStorage instance for the given Firebase app. but NOT AUTHED
const defaultStorage = defaultApp.storage(); // Gets the default FirebaseStorage instance.
const storageRef = ref(normalStorage)
const bucketPath = `personas-out/img-${cuid()}.jpg`
const bucket = defaultStorage.bucket();
// const bucketRef = bucket.ref(); // Property 'ref' does not exist on type 'Bucket'.ts(2339)
const metadata = {
contentType: 'image/jpeg'
};
const imageRef = ref(storageRef, bucketPath);
const file = new File([url], bucketPath, {
type: "image/jpeg",
});
// will happen in the background?
uploadBytesResumable(imageRef, file, metadata)
.then((snapshot: any) => {
console.log('Uploaded', snapshot);
});
return bucketPath
}
You're not supposed to use the Firebase client SDK (firebase/storage) in nodejs code. The client SDKs are meant to work in a browser only. For nodejs code that runs on a backend (which is what you're doing when you deploy to Cloud Functions), you should use the Firebase Admin SDK. The API it exposes for Storage is essentially a thin wrapper around the Google Cloud Storage nodejs library.

retrieve secret firebase service account json

I have this code in nextjs that is supposed to check if a token is valid then sign in the user.
const firebaseAdmin = require("firebase-admin");
const serviceAccount = require ('../secret.json');
export const verifyIdToken = async (token) => {
if (!firebaseAdmin.apps.length) {
firebaseAdmin.initializeApp({
// credential: firebaseAdmin.credential.cert(serviceAccount),
credential: firebaseAdmin.credential.applicationDefault(),
databaseURL: "rtdb.firebaseio.com",
});
}
return await firebaseAdmin
.auth()
.verifyIdToken(token)
.catch((error) => {
throw error;
});
};
I have the windows environment variables set as firebase recommends and switched to using the applicationDefault() since as I understand,
ADC can automatically find your credentials
Problem is the application works only locally. When I deploy the website, the token is not verified and creates errors. I am serving the NextJs app through a cloud function. How can I solve this.
The error is
auth/invalid-credential
Must initialize app with a cert credential or set your Firebase project
ID as the GOOGLE_CLOUD_PROJECT environment variable to call verifyIdToken().
What the app is supposed to do is do a check server side to determine if a token is valid.
As below
export async function getServerSideProps(ctx) {
try {
const cookies = nookies.get(ctx);
const token = await verifyIdToken(cookies.token);
// the user is authenticated!
const { uid, email } = token;
return {
props: {
userData: {
email: email,
uid: uid,
},
},
};
} catch (err) {
console.log(err.code)
console.log(err.message)
return { props: {
} };
}
}
The auth/invalid-credential error message means that the Admin SDK needs to be initialized, as we can see in the Official Documentation.
The credential used to authenticate the Admin SDKs cannot be used to
perform the desired action. Certain Authentication methods such as
createCustomToken() and verifyIdToken() require the SDK to be
initialized with a certificate credential as opposed to a refresh
token or Application Default credential.
And for the ID token verification, a project ID is required. The Firebase Admin SDK attempts to obtain a project ID via one of the following methods:
If the SDK was initialized with an explicit projectId app option, the SDK uses the value of that option.
If the SDK was initialized with service account credentials, the SDK uses the project_id field of the service account JSON object.
If the GOOGLE_CLOUD_PROJECT environment variable is set, the SDK uses its value as the project ID. This environment variable is available for code running on Google infrastructure such as App Engine and Compute Engine.
So, we can initialize the Admin SDK with a service (and fulfill the second option); but, the first thing to do is authenticate a service account and authorize it to access Firebase services, you must generate a private key file in JSON format.
To generate a private key file for your service account you can do the following:
In the Firebase console, open Settings > Service Accounts.
Click Generate New Private Key, then confirm by clicking Generate Key.
Securely store the JSON file containing the key.
Once you have your JSON file, you can set a environment variable to hold your private key.
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/service-account-file.json"
And then, use it in your code like this:
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: 'https://<DATABASE_NAME>.firebaseio.com'
});
In the end I downloaded Gcloud tool and setting the GOOGLE_APPLICATION_CREDENTIALS environment variable from the tool worked. The function could then work with credential: firebaseAdmin.credential.applicationDefault(),

How to access a Google Firestore collection from within a Cloud Function referencing a different Firestore collection?

The following (from a React site) is not working . I've been in the docs for hours without success. Any ideas?
import firebase = require("../../node_modules/firebase");
import * as functions from "firebase-functions";
exports.onSomeCollectionCreate = functions
.firestore
.document("some-collection/{someCollectionId}")
.onCreate(async(snap, context) => {
firebase
.firestore()
.collection("another-collection/{anotherCollectionId}")
.add({ some: "data" });
}
);
Some terminal feedback:
⚠ functions[onSomeCollectionCreate(region)]: Deployment error.
Thank you for reading.
In a Cloud Function, you should use the Admin SDK in order to interact with the Firebase services, see the doc for more details.
The following should therefore work:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Cloud Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
exports.onSomeCollectionCreate = functions
.firestore
.document("some-collection/{someCollectionId}")
.onCreate(async(snap, context) => {
return admin. // note the return
.firestore()
.collection("another-collection")
.add({ some: "data" });
}
);
Note two additional points:
You should not pass to the collection() method a string with a slash (/), since Collection references must have an odd number of segments.
Note that we return the Promise returned by the add() method. See the doc here for more details on this key point.

Firestore admin in Node.js Missing or insufficient permissions

I am trying to access Firestore using Firebase Admin on Node.js v8.4.0 in a Firebase Cloud Function running locally using firebase functions:shell on Windows 10.
firebase -v 4.2.1
"dependencies": {
"firebase-admin": "^6.0.0",
"firebase-functions": "^2.0.5"
}
After attempting to use firebase admin from my apps code, I attempted to run the quick start example in https://github.com/firebase/functions-samples/blob/master/quickstarts/uppercase-firestore/functions/index.js.
This is the actual code run:
'use strict';
// [START all]
// [START import]
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp({})
// [END import]
// [START addMessage]
// Take the text parameter passed to this HTTP endpoint and insert it into the
// Realtime Database under the path /messages/:documentId/original
// [START addMessageTrigger]
exports.addMessage = functions.https.onRequest((req, res) => {
// [END addMessageTrigger]
// Grab the text parameter.
const original = req.query.text;
// [START adminSdkAdd]
// Push the new message into the Realtime Database using the Firebase Admin SDK.
return admin.firestore().collection('messages').add({original: original}).then((writeResult) => {
// Send back a message that we've succesfully written the message
return res.json({result: `Message with ID: ${writeResult.id} added.`});
});
// [END adminSdkAdd]
});
// [END addMessage]
// [START makeUppercase]
// Listens for new messages added to /messages/:documentId/original and creates an
// uppercase version of the message to /messages/:documentId/uppercase
// [START makeUppercaseTrigger]
exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
.onCreate((snap, context) => {
// [END makeUppercaseTrigger]
// [START makeUppercaseBody]
// Grab the current value of what was written to the Realtime Database.
const original = snap.data().original;
console.log('Uppercasing', context.params.documentId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an 'uppercase' sibling in the Realtime Database returns a Promise.
return snap.ref.set({uppercase}, {merge: true});
// [END makeUppercaseBody]
});
// [END makeUppercase]
// [END all]
However, I still get the permission denied error.
This is the output I get:
firebase > makeUppercase({original:'alphabets'},{params:{documentId:'mydoc'}})
'Successfully invoked function.'
firebase > info: User function triggered, starting execution
info: Uppercasing mydoc alphabets
info: Function crashed
info: { Error: 7 PERMISSION_DENIED: Missing or insufficient permissions.
at Object.exports.createStatusError (C:\projects\myproject\functions\node_modules\grpc\src\common.js:87:15)
at Object.onReceiveStatus (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:1188:28)
at InterceptingListener._callNext (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:564:42)
at InterceptingListener.onReceiveStatus (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:614:8)
at callback (C:\projects\myproject\functions\node_modules\grpc\src\client_interceptors.js:841:24)
code: 7,
metadata: Metadata { _internal_repr: {} },
details: 'Missing or insufficient permissions.' }
My security rules are completely open but that did not resolve the error.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
I also thought that this might be an authentication issue so I have tried the following to initialize the app:
1
admin.initializeApp()
2
admin.initializeApp(functions.config().firebase);
3
var serviceAccount = require('path/to/serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://<DATABASE_NAME>.firebaseio.com'
});
The last one with my own credentials file and project configuration. All of these attempts still give me the missing permissions error.
Update:
I deployed these functions to the cloud and they seem to be working perfectly but when running locally, I'm still getting a Error:7 Permission Denied.
Update 2:
Set application default credentials using gcloud auth application-default login as per suggestion by #Doug Stevenson. Ensured environment variable GOOGLE_APPLICATION_CREDENTIALS is not set. Attempted the code in 1,2 and 3 above as well as 4 below with no success. Encountered the same error.
4
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "https://myapp-redacted.firebaseio.com"
});
I hope that by now you've already resolved your issue. The same thing just happened to me and I'm sharing what it was in my case with the hope that it will help others.
We manage several firebase projects - productions, dev, staging etc.
we init the admin sdk with this:
let serviceAccount = require("../serviceAccountKey.json");
const databaseUrl = functions.config().environment.databaseurl
const storageBucket = functions.config().environment.storagebucket
const isDev = "true" === functions.config().environment.dev
// if dev environment
if (isDev) {
serviceAccount = require("../serviceAccountKey-dev.json");
}
admin.initializeApp({
projectId: functions.config().environment.projectid,
credential: admin.credential.cert(serviceAccount),
databaseURL: databaseUrl,
storageBucket: storageBucket
});
You know how you need to do
firebase functions:config:get > .runtimeconfig.json
for the emulation to work. Well, my runtimeconfig was containing the wrong configuration. I was loading my serviceAccountKey-dev, but I was trying to access a different project. The second I fixed my runtimeconfig - it worked for me.
I had the same issue and was resolved by going to the cloud console then granting the role Firebase Admin SDK admin service agent to the app engine service account which is in the following format {PROJECT_ID}#appspot.gserviceaccount.com.
Ultimately I just had to run gcloud auth application-default login to make sure I was logged in with the correct Google account.
It is required that the client, the firebase function, to have access to the resource, firebase firestore. Following the least privilege principle you would need to:
Create a role within Google Cloud IAM with the following permissions:
datastore.entities.get, datastore.entities.update.
Also in IAM, create a service account and assign it the recently created role.
Update your firebase function selecting the new service account.
I have not found a way to assign the service account while deploying with firebase-cli. Here a guide for configuring permissions of the functions https://cloud.google.com/functions/docs/securing/function-identity.

firebase-admin admin.database() makes infinite loop without returning anything

Hi im new to firebase and in my first test i followed the documentation of how to retrieve data from a realtime database i configured the rules for accessing + writing the code also generated the key which is needed for firebase-admin SDK
and here is the code
var admin = require("firebase-admin");
var serviceAccount = require("./generatedPrivateKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://alan-b6de4.firebaseio.com/"
});
var db = admin.database();
var ref = db.ref("users");
ref.once("value", function(snapshot) {
console.log(snapshot.val());
});
every thing looks fine this ,the problem is that when i run this code it loops without returning anything the console screen just holds but when i take the same code without any modification to another computer it works just fine :(
I use latest stable version of nodeJs which is 8.9.4

Resources