Developing an SDK - How to send Firebase events to SDK firebase account, not the app one? - firebase

Firebase is great, it helps in so many ways, and well-integrated with crashalytics, Google Analytics, Google Tag Manager, you name it.
We are building a mobile SDK, however, it seems that you can only link to Firebase on the app level (you can't initialise your own SDK to send to a different account than App's Firebase account).
Is there a workaround to allow me as an SDK developer to collect events, crash reports,.... to a Firebase account related to the SDK alone, regardless to the app that is using the SDK?
Many thanks

You can initialize more than one instance of the SDK at once. However, you should make consumers of your SDK aware that it is doing so and they should have a clear way to opt-out.
When an application is launched, the FirebaseInitProvider will handle initialization of the default application instance. This default app instance has a name of "[DEFAULT]".
If you wanted to have a second user logged in, make use of a separate project, make use of a secondary database, and so on - you can just initialize another application instance. Importantly (taken from the docs), any FirebaseApp initialization must occur only in the main process of the app. Use of Firebase in processes other than the main process is not supported and will likely cause problems related to resource contention.
This is done using FirebaseApp.initializeApp() like so:
FirebaseApp nameApp = FirebaseApp.initializeApp(context, config, "name");
To replicate the default instance so you can have more than one user logged in, you can use:
FirebaseApp defaultApp = FirebaseApp.getInstance();
FirebaseApp secondaryApp = FirebaseApp.initializeApp(
defaultApp.getApplicationContext(),
defaultApp.getOptions(),
"secondary"
);
To use a separate instance entirely, you load your configuration into a FirebaseOptions object using:
FirebaseOptions sdkAppOptions = new FirebaseOptions.Builder()
.setApiKey(sdkApiKey)
.setApplicationId(sdkAppId)
.setDatabaseUrl(sdkDatabaseUrl)
.setGcmSenderId(sdkGcmSenderId)
.setProjectId(sdkProjectId)
.setStorageBucket(sdkStorageBucket)
.build();
FirebaseApp sdkApp = FirebaseApp.initializeApp(
sdkContext,
sdkAppOptions,
"com-example-mysdkproject" // <- use namespace as best practice
);
Once you have an instance of FirebaseApp, you can then pass it through to other the other services using:
FirebaseAuth sdkAuth = FirebaseAuth.getInstance(sdkApp);
// or
FirebaseApp sdkApp = FirebaseApp.getInstance("com-example-mysdkproject");
FirebaseAuth sdkAuth = FirebaseAuth.getInstance();

Related

firebase-admin, is it contradicting to use default credentials and serviceAccountId in same initialization?

I initialize firebase-admin like so for firebase functions:
const admin = require('firebase-admin')
const { applicationDefault } = require('firebase-admin/app')
admin.initializeApp({
credential: applicationDefault(),
serviceAccountId: 'firebase-adminsdk-hash#project-id.iam.gserviceaccount.com',
})
I had to add the serviceAccountId so that I could access another IAM permission. But I'm not sure if it's now unnecessary use applicationDefault() as well. Does anyone know if this is considered redundant / contradicting?
The short answer, in my opinion, would be: yes, it is redundant and inconsistent to specify the applicationDefault parameter as it is implicitly populated from the FIREBASE_CONFIG environment variable as explained in this previous question. (Please be aware the initialization can be different depending on the environment).
Here is a comparison from initializing the service account and the Cloud Functions together and impersonating the service account.
To get a more detailed explanation, we will need to dive into the documentation; we can see that the initializeApp() function has the AppOptions parameter (interface), where you can specify the serviceAccountId.
Finally, you can check all the functions from firebase-admin/app module, and get this description for the applicationDefault(httpAgent) function:
Returns a credential created from the Google Application Default Credentials that grants admin access to Firebase services. This credential can be used in the call to initializeApp().
Google Application Default Credentials are available on any Google infrastructure, such as Google App Engine and Google Compute Engine. See Initialize the SDK for more details.

How should be the security rules while using two different firebase project accessing each other's data?

I'm using two different firebase project in two different flutter android apps. One of the firestore-database is holding some store's data and the other one is holding users Authentication-Database. Let's say, name of the first project is 'store-owners' and name of the second one is 'customer'. I want 'store-owners' data is accessible only and only if the user is registered in 'customer' project. How should be the security rules for the 'store-owners' project? Thanks :)
That's impossible with security rules only. You cannot authenticate users of one project from security rules of others. You would have run your own server or Cloud functions, initialize both the projects in it and then use the Admin SDK access the through it.
// Initialize the default app
admin.initializeApp(defaultAppConfig);
// Initialize another app with a different config
var otherApp = admin.initializeApp(otherAppConfig, 'other');
console.log(admin.app().name); // '[DEFAULT]'
console.log(otherApp.name); // 'other'
// Use the shorthand notation to retrieve the default app's services
const defaultAuth = admin.auth();
const defaultDatabase = admin.database();
// Use the otherApp variable to retrieve the other app's services
const otherAuth = otherApp.auth();
const otherDatabase = otherApp.database();
That way you can write functions which will authenticate users from your auth projects and then read the data from the project where the data is stored.
You can read more about initializing multiple apps in the documenation.
To summarize the flow:
Call function => Authenticate User => Return relevant data
You would have to manually check if the user can access the requested data or not. Firebase Custom Claims may be handy to do that.

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.

Firebase functions authorize only requests from Firebase hosting app

I have a simple Firebase Hosting web application (based on a Vue app) which invokes Firebase Function (Google cloud function):
import firebase from "firebase/app";
import "firebase/functions";
firebase.initializeApp(firebaseConfig);
let functions = firebase.app().functions("us-west4");
let testFunction = functions.httpsCallable("testFunction");
and corresponding functions index.js file:
const functions = require("firebase-functions");
exports.testFunction = functions.region("us-west4").https.onCall(async (data, context) => {
console.log("Very important things here");
return {"response": "data"};
});
From security perspective is it possible to
Allow this invocation only from my domain name (Firebase hosting) myhostedapp.web.app
Check for any kind of authentication (e.g. token) that my JS app provides during the request?
I've tried accessing context.auth property (see docs) buth seems like some kind of service account is required and this cannot be used when called from Firebase hosting web application.
Basically I don't want my function to be publicly accessible (simple invocation via trigger url), so any advice or best practice for securing Firebase Hosting + Functions would be appreciated.
Firebase just released a new feature called App Check that does precisely this: it allows the Cloud Functions in your project to only be invoked from apps that are registered in that project.
For web apps this happens through reCAPTCHA v3, which . Then once you enable enforcement of the check on Cloud Functions, it will reject any requests coming from other sources.
You'll typically want to combine App Check with your current user-based approach, so that you can easily block calls from outside your web app, but also still ensure authenticated users only can make calls that they're authorized for.

Connect to different firebase types

I'm new to flutter and I'm working on a project where I need to retrieve data from two different firebase firestores (web and flutter) and show them in different places in my app so I'm wondering if I could do it due to web firebase has been made for a separate web project.
If I correctly understand, you can use two different instances of a Firestore Database, as explained in the cloud_firestore package documentation:
You can get an instance by calling FirebaseFirestore.instance. The
instance can also be created with a secondary Firebase app by calling
FirebaseFirestore.instanceFor.
FirebaseApp secondaryApp = Firebase.app('SecondaryApp');
FirebaseFirestore firestore = FirebaseFirestore.instanceFor(app: secondaryApp);

Resources