Working with multiple Firebase projects in an Android app

Firebase provides a bunch of features to use together in your app, provided by a project that you create at the Firebase console. Normally, it’s sufficient to have all your app’s resources provided by a single project, but there are times when you want a single app to be able to access data from multiple projects. For example, you may need to access data from two different databases, and be able to authenticate users to access each one. I’ll show you how that’s done in this post.

First, a little terminology:

Legacy Firebase.com Project

A project created on the legacy console, associated with a Firebase Database that has not been upgraded to the new console.

Google API Project

A project used for accessing Google APIs - usually from https://console.developers.google.com or https://console.cloud.google.com

Firebase Project

A project created on the new Firebase console. Every Firebase project is also a Google API project underneath.

App

A client for a specific platform. Each project can have multiple apps associated with it..

Upgraded legacy Firebase.com project along with existing Google API project

One particular scenario occurs for developers who want to upgrade their existing legacy Firebase.com database to a new Firebase project, while also being able to use services from another Google API project. The upgraded legacy project becomes a new Firebase project, and that needs to be used in tandem with the Google API project that provides Google Sign-In authentication for existing users.

The challenge here is that for the Google Sign-In component to work on Android, it requires a SHA-1 (the fingerprint of the key used to sign the APK) and package name (e.g. com.foo.bar) to be registered for the app. This combination allows Google Sign-In to know which Google API project is being used by a particular app. A given pair of SHA1 and Package Name is globally unique within Google (and Firebase projects), so if you try to add the same pair SHA-1 and package name to an upgraded Firebase project, you get an error that the OAuth2 client already exists (in the Google API project):

Warning: If you see this, don’t delete your existing client ID for apps in production! This will break your app for your existing users. The right choice is to create a new app with the same your package name in the Firebase console for the upgraded project, but not to include a SHA1.

Now implement Google Sign In with Firebase Auth as normal. At one point you will have to configure your Google Sign Options object:

GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
              .requestIdToken(getString(R.string.default_web_client_id))
              .requestEmail()
              .build(); 

The _default_web_client_id_ string here is used to set the audience field of the ID token. The value comes from the google-services.json file, which is from the Firebase project rather than the Google project. You’ll need to replace it with a client ID from the Google project. You can use any Web client ID, or create a new one:

Next, back in the Firebase project, whitelist the client ID you just set for the GoogleSignInOptions in the Auth > Sign In Providers > Google section of the Firebase console.

Be sure to re-download your google-services.json and add it to your Android app. At this point, your Firebase project will accept Google ID tokens generated by your Google project - so your Android app will happily sign in to Google using the Google project, then authenticate with your Firebase project using the Google ID token following the normal approach. You’ll be able to make authenticated calls to Google APIs associated with the Google API project, and authenticated calls to Firebase APIs using the Firebase project.

Accessing the Databases from two different Firebase projects

In the previous situation, we had a single Firebase project which needed to also access a Google project. That works because the APIs are separate. However, sometimes you need to access different projects using the same APIs - for example, accessing multiple database instances.

For Android apps using Firebase, there is a central FirebaseApp object that manages the configuration for all the Firebase APIs. This is initialized automatically by a content provider when your app is launched, and you typically never need to interact with it. However, when you want to access multiple projects from a single app, you’ll need a distinct FirebaseApp to reference each one individually. It’s up to you to initialize the instances other than the default that Firebase creates for you.
For example, to connect to the default Firebase Database instance, we implicitly use the default Firebase app:

FirebaseDatabase database = FirebaseDatabase.getInstance();

To connect to another Firebase Realtime Database from another project, you first need to initialize a FirebaseApp instance for that other Firebase project, and give it an identifier - in this case “secondary”:

FirebaseOptions options = new FirebaseOptions.Builder()
       .setApplicationId("1:530266078999:android:481c4ecf3253701e") // Required for Analytics.
       .setApiKey("AIzaSyBRxOyIj5dJkKgAVPXRLYFkdZwh2Xxq51k") // Required for Auth.
       .setDatabaseUrl("https://project-1765055333176374514.firebaseio.com/") // Required for RTDB.
       .build();
FirebaseApp.initializeApp(this /* Context */, options, "secondary");

Then, you can access the database using the same client APIs, but this time specifying which project you want to access by passing the relevant FirebaseApp to FirebaseDatabase.getInstance():

// Retrieve my other app.
FirebaseApp app = FirebaseApp.getInstance("secondary");
// Get the database for the other app.
FirebaseDatabase secondaryDatabase = FirebaseDatabase.getInstance(app);

Authenticating to two different Firebase Databases

We can combine the two techniques above to allow sharing authentication data between Firebase project whenever you have an external ID to join on.

For example, if our app allows sign in with Google Sign-In, and we have configured our database rules in our default and secondary projects to require authentication, we can use the same Google credential to log in to both systems.

First, we set up the app for Google Sign-In on the default project as normal. Then, we get the underlying client ID from the default project. A client ID is just an identifier for a given app client (web, Android, iOS) that is usually contained within the client itself. A project can have several client IDs, but the one we need to whitelist is the one specified in the requestIdToken call to the GoogleSignInOptions builder:

.requestIdToken(getString(R.string.default_web_client_id))

You can usually find it as the first client_id with type “3” in the google-services.json. Mine was:

{
 "client_id": "56865680640-e8mr503bun5eaevqctn4u807q4hpi44s.apps.googleusercontent.com",
 "client_type": 3
},

With that in hand, we go to the Google panel of the Auth > Sign In Providers section of the secondary project, where we can whitelist the client ID.

Now we can grab the same GoogleSignInAccount object from the Google Sign-In result and authenticate to both the default and secondary apps:

AuthCredential credential = GoogleAuthProvider.getCredential(account.getIdToken(), null);
FirebaseAuth.getInstance().signInWithCredential(credential);

FirebaseApp app = FirebaseApp.getInstance("secondary");
FirebaseAuth.getInstance(app).signInWithCredential(credential);

With one sign in from the user, they are authenticated against both projects.

Sharing UIDs Between Projects

One challenge here is that the Firebase user IDs on each project will be different. For example, using the same Google credential I get these two UIDs:

Default Auth UID: 0960868722032022577213DA4EA8B7A1683D92B405DD
Secondary Auth UID: 7h6XOeSxmkNsSseFJ1jU31WZHDP2

If the app doesn’t offer account linking, we can use the Google (or Facebook, Twitter, etc.) user ID for things like database structures and security rules. However, if we need the same user ID in each project, or we’re using email/password or anonymous auth, the situation is slightly trickier.

Luckily, it can be resolved using custom auth facilities, along with some server side code, since custom auth tokens get to specify their own UID!
This time, we don’t whitelist anything on the secondary project, but we do download the service account for both it and our default projects. In our Android client, we first sign in and grab the Firebase ID token from the FirebaseAuth client:

Note: We can use any sign in provider we want here! We’re just using the custom token to link our user IDs across projects.

firebaseAuth.getCurrentUser().getToken(false /* forceRefresh */)
       .addOnCompleteListener(new OnCompleteListener() {
   @Override
   public void onComplete(@NonNull Task task) {
       String token = task.getResult().getToken(); // Send this to the server.
   }
});  

We send that to our server, where we use it create a Firebase custom token. Just as on Android, we need to initialise each of our apps, though we use service accounts as we’re server side (here we’re using the Java server SDK, but you could use NodeJS similarly).

FirebaseOptions options = new FirebaseOptions.Builder()
  .setServiceAccount(new FileInputStream("default-service-account.json"))
  .build();
FirebaseApp.initializeApp(options);


FirebaseOptions secondaryOptions = new FirebaseOptions.Builder()
  .setServiceAccount(new FileInputStream("secondary-service-account.json"))
  .build();
FirebaseApp.initializeApp(secondaryOptions, "secondary");

The primary app is used to verify the token coming from the client, and the secondary to create the custom auth token with the appropriate UID set:

// Verify the ID token using the default app.
FirebaseAuth.getInstance().verifyIdToken(idToken)
  .addOnSuccessListener(new OnSuccessListener() {
    @Override
    public void onSuccess(FirebaseToken decodedToken) {
      String uid = decodedToken.getUid();
      System.out.println("User " + uid + " verified");
      FirebaseApp app = FirebaseApp.getInstance("secondary");
      String customToken = FirebaseAuth.getInstance(app).createCustomToken(uid);
      // TODO: Send the token back to the client!
    }
}); 

Back in the Android app, we take the custom token from the server and use it to authenticate to the secondary project.

FirebaseApp app = FirebaseApp.getInstance("secondary");
FirebaseAuth.getInstance(app).signInWithCustomToken(token);

Now the Firebase uid in both projects matches.


Default Auth UID: 0960868722032022577213DA4EA8B7A1683D92B405DD
Secondary Auth UID: 0960868722032022577213DA4EA8B7A1683D92B405DD

For iOS and web as well

Hopefully, this helps offer some options for dealing with multiple Firebase projects from a single app. If you’re wondering whether the same thing works on iOS and the web - it absolutely does. You just need to use the equivalent to Android’s FirebaseApp to create a reference to the secondary project.

With JavaScript, you use firebase.app:

var config = {
  apiKey: "",
  authDomain: ".firebaseapp.com",
  databaseURL: "https://.firebaseio.com",
  storageBucket: ".appspot.com",
  messagingSenderId: "",
};

var secondary = firebase.initializeApp(otherAppConfig, "secondary");
var secondaryDatabase = secondary.database();  

And with iOS you use FIRApp:

// Alt: load from plist using |FIROptions(contentsOfFile:)|
let options =  FIROptions(googleAppID: googleAppID, bundleID: bundleID, GCMSenderID: GCMSenderID, APIKey: nil, clientID: nil, trackingID: nil, androidClientID: nil, databaseURL: databaseURL, storageBucket: nil, deepLinkURLScheme: nil)

FIRApp.configure(withName: "secondary", options: fileopts)
guard let secondary = FIRApp.init(named: "secondary")
      else { assert(false, "Could not retrieve secondary app") }

let secondaryDatabase = FIRDatabase.database(app: secondary);

For more information and links, take a look at the new Configuring Your Firebase Project page in the Firebase documentation.