Nothing spoils a great game for a user like a crash, and most of us have been there too: you’re just about to solve the puzzle, defeat the final boss, or cross the finish line, and then...crash.
For Tapps Games, a Brazilian game developer, it was particularly important that players had a stable and high performing gaming experience during one of the feature rollouts to their popular game, Vlogger Go Viral. With more than 11M monthly active users, the Tapps team didn’t have time to investigate every negative review manually — it would take days, leaving a considerable number of users waiting for resolution.
To solve this, Tapps Games enabled Firebase Crashlytics velocity alerts, which let them know right away when there was an increase in the severity of crashes occurring in the Vlogger Go Viral game. Crashlytics also helped them prioritize, identify and track the state and sequences of events that led to the crash using custom keys and custom logs. The Vlogger Go Viral team then used Remote Config to shut down the problem area in stages and used staged rollouts on the Google Play console to slowly release the new version to a subset of it’s players before moving ahead to a full rollout
Check out the full case study to find out how Tapps Games used Crashlytics and Remote Config to increase crash free user rate and improve ratings, and learn more ways Firebase can help you build and grow your game.
We’re excited to announce several new features that make developing with Firebase Hosting even better!
Our new integration with Cloud Logging gives you access to web request logs for your Hosting sites. Cloud Logging, previously known as Stackdriver Logging, makes it easy to view, search, and filter logs. You can learn from where and when you have visits to your site, your site's response statuses, the latency of end user requests, and more.
To get started, link your Firebase project to Cloud Logging in the Cloud Logging integration card in the Firebase console or visit the Hosting docs to learn more.
Hosting will now compress your assets with Brotli encoding. We’ll automatically serve the smallest, best-compressed version of your content, based on what your user's client is able to handle.
Brotli compression gives you a boost in performance because assets are smaller, use less bandwidth, and are delivered more quickly to your end users. This means you’ll have lower bandwidth costs, while your end users will enjoy faster sites.
The best part? You’ll get this automatically the next time you deploy.
We know it’s important to you to provide a great experience for your users everywhere. Firebase Hosting now supports serving country and language specific content, backed by the power of our global CDN. Previously, the best strategy was to use Cloud Functions to look at the country and/or language code and reply with a redirect as necessary.
Firebase Hosting’s i18n rewrites allow developers to serve different content, depending on a user's language preferences or country location. In your public directory, create an i18n directory containing separate folders for each language/country combination, then add the i18n field to your project's firebase.json config. I18n rewrites also work for localized 404 pages. Learn more about using i18n rewrites in our documentation.
i18n
firebase.json
We hope you all are excited for these new features, but stay put because we have even more coming in the future!
The latest Google Analytics for Firebase SDK release now makes it possible for you to log the screen_view event manually for both Android and iOS!
screen_view
When you add the Google Analytics for Firebase SDK to your app, there are a number of different events that start getting collected automatically - including the screen_view event for your Android and iOS apps.
This event is incredibly important to understand your users' behavior since it can tell you the number of users who have visited each screen in your app, and which screens are the most popular. In addition, this event powers the Behavior reports offered in Google Analytics’s App + Web properties, which can tell you how much time users spend in each screen. It also powers the Path Analysis tool where you can discover the most popular navigational paths within your app.
Behavior overview reporting page
Path analysis technique
Most of the automatically collected events, including screen_view, use protected event names that prevent you from being able to manually log events with the same name. There are good reasons for doing this because a lot of these events power other Firebase products, like A/B Testing and Firebase Predictions, and first class reporting features in Google Analytics.
But we’ve heard a lot of your feedback - and screen_view is one event for which you wanted more control over how and when the event gets logged - in addition to having the option to continue logging events automatically.
So our latest release now makes this possible. This release also marks the deprecation of the setCurrentScreen (on Android) and setScreenName (on iOS) methods which will be replaced by the new calls to manually log the screen_view event.
There are many benefits to having more control for when the screen_view event gets logged:
For example, you might only want to log a screen_view event when a user has spent at least some minimum amount of time on the screen.
In fact, you might recall that Google Analytics previously logged session_start events ten seconds after an app first loaded into the foreground. While this has changed and session_start events are logged immediately, there can be instances in your app when it makes sense to wait a few seconds before logging a meaningful screen_view.
session_start
In a video streaming app, users may often load up a title selection screen and quickly scroll through and select a series they’ve already been watching to pick up where they left off. They might not really be searching for new titles, even though they’re visiting the title screen, and you'll want to only log screen views here when you really know that they’re looking for new titles, not just quickly scrolling through it.
Your app might have subsections in some screens (think “Container View Controllers” with inner Child VCs or Fragments), or in the case of a game or a Flutter app, be driven by one screen that powers other views that present meaningful actions a user can take, or represent places in your app where users will spend a significant amount of time. In such cases, you might want to log a screen_view event manually, and add additional event parameters that represent the subsections of your app that your users spend more time exploring.
A good example here is an app that provides a customer support chat interface that overlays onto an existing screen the user is viewing. In such cases, you might want to log a manual screen_view event that represents that chat interface and that can collect more information in added event parameters - like which topic or which screen the user was on before they opened the support chat window.
In addition to the reasons above, there was an issue on iOS devices with automatically collected screen_view events using the soon-to-be-deprecated setScreenName method in which the events were dual-logged. This change also corrects this issue so it is no longer a factor.
setScreenName
You can log a manual screen_view just like you would any other event in Google Analytics. You can also include the two optional parameters (i) screen_name and (ii) screen_class, as well as any custom event parameters you want to include. The two optional parameters take the place of the firebase_screen and firebase_screen_class event parameters that get passed into automatically collected screen_view events.
screen_name
screen_class
firebase_screen
firebase_screen_class
Specifically for iOS, this would look like:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // After enough time has passed to make this screen view significant. Analytics.logEvent(AnalyticsEventScreenView, parameters: [ AnalyticsParameterScreenName: screenName!, AnalyticsParameterScreenClass: screenClass!, MyAppAnalyticsParameterFitnessCategory: category! ]) }
And for Android:
@Override public void onResume() { super.onResume(); // After enough time has passed to make this screen view significant. Bundle bundle = new Bundle(); bundle.putString(FirebaseAnalytics.Param.SCREEN_NAME, screenName); bundle.putString(FirebaseAnalytics.Param.SCREEN_CLASS, screenClass); bundle.putString(MyAppAnalyticsConstants.Param.TOPIC, topic); mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, bundle); }
As mentioned before, the new API will allow you to log manual screen_view events along with the option to continue collecting screen_view events automatically. Since both are possible at the same time, let’s get into some questions to learn more about how the new feature works with the existing automatic event collection.
Yes. While you can use both together at the same time, you can disable automatic event collection for screen_view events if you’d like. On iOS, set FirebaseAutomaticScreenReportingEnabled to “NO” in your info.plist. On Android, set google_analytics_automatic_screen_reporting_enabled to “false” in your manifest.
FirebaseAutomaticScreenReportingEnabled
google_analytics_automatic_screen_reporting_enabled
The setScreenName and setCurrentScreen method calls were used to set a custom-defined screen name as the firebase_screen_name parameter value included with all automatically logged screen_view events. If you want to continue collecting custom screen names on your screen_view events, you will need to log the screen_view event manually and pass in the screen_name event parameter (likely using the same value you were using in the soon-to-be deprecated methods).
setCurrentScreen
firebase_screen_name
Note that for automatically reported screen_view events, the screen name value will be left blank, and the screen class value will be set to that of the most recently shown screen.
If you disable automatic screen reporting, some values are still accessible and can be automatically added to your manually logged screen_view events, such as the firebase_screen_class parameter.
This is because parameters like the screen class are first-class metrics that feed into Google Analytics’ Behavior Reports, and so we’ve gone the extra mile to automatically populate this field using information we know on the client. On Android, the current activity will be used to populate the screen class parameter. On iOS, if you make a call to the logEvent() method, the root UIViewController will be used to populate the screen class value. You can always overwrite this value by passing in a different screen_class parameter, though. On both platforms the screen name will be left blank (unless you specify it), and the value “not set” will be displayed in the Google Analytics console.
logEvent()
Yes, all event parameters that are reported along with automatically collected screen_view events will also be automatically reported with manually logged screen_view events - with one temporary exception for the engagement_time_msec on iOS devices. We are working on implementing support to have the engagement time parameter included with manually reported screen_view events on iOS, but for the time being this parameter isn’t included and instead, is reported on a designated user_engagement event.
engagement_time_msec
We hope you enjoy the new ability to manually log your screen view events, and would love to hear more feedback from you about this feature, or about the Google Analytics for Firebase SDK for your apps in general. Please reach out to us at our Firebase Google Group, on StackOverflow or on our Firebase Support channel to submit your feature requests.
Firebase is committed to building an easy to use and robust backend service for Unity developers. Recent changes in the build system accompanying Unity 2020.1 have caused some of the integrations that the Firebase games team built for Unity to stop functioning correctly. Our team is aware of this, and is working hard to rectify the situation. Until then, we recommend that you continue to use Unity 2019 for the best developer experience. For those of you that are on Unity 2020.1, the rest of this post details the workarounds needed to get things up and running.
The Firebase SDK for Unity includes native C++ code and Android specific logic. It also requires some build time operations for all of its products to function properly. Because of this, we have written a number of tools and custom routines that make strong assumptions about Unity’s build process. These continue to work to simplify integration back to Unity version 5, but in Unity 2020.1 the engine overhauled its build system invalidating our previous assumptions. This means that the mechanism by which AndroidX and Jetifier are integrated no longer functions as well as the mechanism by which the information in google-services.json is transmitted to your final Android game. This is required to initialize the Android side of Firebase and to properly hook it up to the Firebase backend.
google-services.json
Further, components that the Crashlytics SDK requires to function are no longer generated. In the worst case scenario, this manifests in a crash on launch.
Although we recommend sticking with the 2019 branch of Unity for the best experience, Unity’s new build system is very similar to a typical Android Java project. This simplifies the workaround we will highlight below, and will mirror some steps in the getting started with Android guide.
Note that because you are performing operations that the Firebase SDK would typically handle automatically, you may have to undo any changes when a full fix is rolled out by our team.
The first fix is to help Firebase find your google-services.json file. Normally the Firebase Unity Plugin would do this automatically, replicating the work of the Google Services plugin Android developers may be familiar with. When you add or update your google-services.json file, the plugin creates a version that Android reads as a “resource” at Assets/Plugins/Android/Firebase/res/values/google-services.xml. Typically this would get copied into your final project automatically, but we can do a little manual work to ensure it gets included!
Assets/Plugins/Android/Firebase/res/values/google-services.xml
First generate a custom mainTemplate.gradle and gradleTemplate.properties (which we’ll need in a moment) via Project Settings/Player/Publishing Settings if you haven’t already:
mainTemplate.gradle
gradleTemplate.properties
Project Settings/Player/Publishing Settings
If you’ve disabled EDM4U’s integration or you just want to be extra careful, now is a great time to also force resolve your Android dependencies from Assets/External Dependency Manager/Android Resolver/Force Resolve. This will update mainTemplate.gradle in a manner that still cuts back on most of the work we’ll have to do:
Assets/External Dependency Manager/Android Resolver/Force Resolve
Now you need to tell gradle where to find the google-services.xml file by adding this to the very bottom of your mainTemplate.gradle:
google-services.xml
android { sourceSets { main { def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") res.srcDirs += (unityProjectPath + '/Assets/Plugins/Android/Firebase/res/values/google-services.xml') } } }
There’s one more fix to get base functionality working in Unity, you’ll see that towards the top of mainTemplate.gradle there are some lines like:
// Android Resolver Repos Start ([rootProject] + (rootProject.subprojects as List)).each { ext { it.setProperty("android.useAndroidX", true) it.setProperty("android.enableJetifier", true) } }
This code used to go through every project in the build and enable AndroidX and Jetifier. Although we could perform a similar modification via gradle, Android developers are generally encouraged to use gradleTemplate.properties - which is supported via Unity’s new build pipeline.
So if you remembered to generate it earlier in the tutorial, add the following lines to the end of gradleTemplate.properties:
android.useAndroidX=true android.enableJetifier=true
At this point, most of Firebase should function. This includes your ability to measure your game’s performance with Google Analytics, use that to customize your player’s experience with Remote Config, and to Authenticate you user to begin your game’s connection social experience.
Crashlytics needs a little more work to get operational since it has to embed itself a little deeper into the build process. To continue, you will need the baseProjectTemplate.gradle (the “project level gradle file”). You’ll also need launcherTemplate.gradle since we need an “Application” for Crashlytics (mainTemplate.gradle is technically a “Library”):
baseProjectTemplate.gradle
launcherTemplate.gradle
There’s no need to Force Resolve dependencies again since EDM4U doesn’t know about these files yet! But you can always do it to be safe (the way I modified mainTemplate.gradle purposely avoids bits of the file that would get updated).
Now open baseProjectTemplate.gradle and look for something that looks roughly like:
allprojects { buildscript { repositories {**ARTIFACTORYREPOSITORY** google() jcenter() } dependencies {
Inside allprojects.buildscript.dependencies (the dependencies { bit in this example), add this code to tell gradle about Crashlytics:
allprojects.buildscript.dependencies
dependencies {
// Add the Crashlytics Gradle plugin. classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0'
and this line of code to the very end of launcherTemplate.gradle to add the Crashlytics plugin to your Application:
// Apply the Crashlytics Gradle plugin apply plugin: 'com.google.firebase.crashlytics'
We are working hard to resolve these issues as soon as possible so you can go back to building games quickly, easily, and confidently, with Firebase and Unity. To those developers who did upgrade to Unity 2020.1 and encountered issues, we apologize for any confusion or difficulty that this has caused. We would like to reassure you all that there is also active work on some exciting updates that will aid in avoiding similar issues in the future and bring more transparency to this process.
If these steps don’t work for you, or a specific Firebase library isn’t working after you’ve completed these steps, feel free to join in the conversation at the Unity GitHub repository or reach out to support and we will be happy to provide further help.
For your reference, I’ve included all of my modified files here. Do not copy these files directly, but use them to verify that you’ve made the proper changes in the proper locations:
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN // Android Resolver Repos Start ([rootProject] + (rootProject.subprojects as List)).each { ext { it.setProperty("android.useAndroidX", true) it.setProperty("android.enableJetifier", true) } } ([rootProject] + (rootProject.subprojects as List)).each { project -> project.repositories { def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") maven { url "https://maven.google.com" } maven { url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/Firebase/Editor/AnalyticsDependencies.xml:18, Assets/Firebase/Editor/AppDependencies.xml:20, Assets/Firebase/Editor/CrashlyticsDependencies.xml:20, Assets/Firebase/Editor/RemoteConfigDependencies.xml:20 } mavenLocal() jcenter() mavenCentral() } } // Android Resolver Repos End apply plugin: 'com.android.library' **APPLY_PLUGINS** dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start implementation 'com.google.firebase:firebase-analytics:17.4.1' // Assets/Firebase/Editor/RemoteConfigDependencies.xml:15 implementation 'com.google.firebase:firebase-analytics-unity:6.15.2' // Assets/Firebase/Editor/AnalyticsDependencies.xml:18 implementation 'com.google.firebase:firebase-app-unity:6.15.2' // Assets/Firebase/Editor/AppDependencies.xml:20 implementation 'com.google.firebase:firebase-common:19.3.0' // Assets/Firebase/Editor/AppDependencies.xml:13 implementation 'com.google.firebase:firebase-config:19.1.4' // Assets/Firebase/Editor/RemoteConfigDependencies.xml:13 implementation 'com.google.firebase:firebase-config-unity:6.15.2' // Assets/Firebase/Editor/RemoteConfigDependencies.xml:20 implementation 'com.google.firebase:firebase-crashlytics:17.0.0' // Assets/Firebase/Editor/CrashlyticsDependencies.xml:13 implementation 'com.google.firebase:firebase-crashlytics-unity:6.15.2' // Assets/Firebase/Editor/CrashlyticsDependencies.xml:20 // Android Resolver Dependencies End **DEPS**} // Android Resolver Exclusions Start android { packagingOptions { exclude ('/lib/arm64-v8a/*' + '*') exclude ('/lib/armeabi/*' + '*') exclude ('/lib/mips/*' + '*') exclude ('/lib/mips64/*' + '*') exclude ('/lib/x86/*' + '*') exclude ('/lib/x86_64/*' + '*') } } // Android Resolver Exclusions End android { compileSdkVersion **APIVERSION** buildToolsVersion '**BUILDTOOLS**' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { minSdkVersion **MINSDKVERSION** targetSdkVersion **TARGETSDKVERSION** ndk { abiFilters **ABIFILTERS** } versionCode **VERSIONCODE** versionName '**VERSIONNAME**' consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD** } lintOptions { abortOnError false } aaptOptions { ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" }**PACKAGING_OPTIONS** }**REPOSITORIES** **IL_CPP_BUILD_SETUP** **SOURCE_BUILD_SETUP** **EXTERNAL_SOURCES** android { sourceSets { main { def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") res.srcDirs += (unityProjectPath + '/Assets/Plugins/Android/Firebase/res/values/google-services.xml') } } }
org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M org.gradle.parallel=true android.enableR8=**MINIFY_WITH_R_EIGHT** **ADDITIONAL_PROPERTIES** android.useAndroidX=true android.enableJetifier=true
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN allprojects { buildscript { repositories {**ARTIFACTORYREPOSITORY** google() jcenter() } dependencies { // If you are changing the Android Gradle Plugin version, make sure it is compatible with the Gradle version preinstalled with Unity // See which Gradle version is preinstalled with Unity here https://docs.unity3d.com/Manual/android-gradle-overview.html // See official Gradle and Android Gradle Plugin compatibility table here https://developer.android.com/studio/releases/gradle-plugin#updating-gradle // To specify a custom Gradle version in Unity, go do "Preferences > External Tools", uncheck "Gradle Installed with Unity (recommended)" and specify a path to a custom Gradle version classpath 'com.android.tools.build:gradle:3.6.0' **BUILD_SCRIPT_DEPS** // Add the Crashlytics Gradle plugin. classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0' } } repositories {**ARTIFACTORYREPOSITORY** google() jcenter() flatDir { dirs "${project(':unityLibrary').projectDir}/libs" } } } task clean(type: Delete) { delete rootProject.buildDir }
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN apply plugin: 'com.android.application' dependencies { implementation project(':unityLibrary') } android { compileSdkVersion **APIVERSION** buildToolsVersion '**BUILDTOOLS**' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { minSdkVersion **MINSDKVERSION** targetSdkVersion **TARGETSDKVERSION** applicationId '**APPLICATIONID**' ndk { abiFilters **ABIFILTERS** } versionCode **VERSIONCODE** versionName '**VERSIONNAME**' } aaptOptions { noCompress = ['.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS**] ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" }**SIGN** lintOptions { abortOnError false } buildTypes { debug { minifyEnabled **MINIFY_DEBUG** proguardFiles getDefaultProguardFile('proguard-android.txt')**SIGNCONFIG** jniDebuggable true } release { minifyEnabled **MINIFY_RELEASE** proguardFiles getDefaultProguardFile('proguard-android.txt')**SIGNCONFIG** } }**PACKAGING_OPTIONS****SPLITS** **BUILT_APK_LOCATION** bundle { language { enableSplit = false } density { enableSplit = false } abi { enableSplit = true } } }**SPLITS_VERSION_CODE****LAUNCHER_SOURCE_BUILD_SETUP** // Apply the Crashlytics Gradle plugin apply plugin: 'com.google.firebase.crashlytics'
Firebase Remote Config allows developers to control the appearance and the behaviour of their apps without having to publish an app update. You can build your app with in-app default configurations and later change those configurations by updating your project’s Remote Config template.
We are happy to announce that you can now manage your Remote Config templates directly from the Admin Node.js SDK.
Typically, you would use the Firebase console to specify and update Remote Config templates manually. However, sometimes you need more automation and server-side control of these settings, such as scheduling Remote Config updates or importing configurations from your own proprietary systems and other Firebase projects. Until now the Remote Config REST API was the only way to implement such use cases, which at times could be cumbersome.
As of version 8.13.0, the Admin SDK introduced a Remote Config API that simplifies the process of creating, accessing, and updating your Remote Config templates from your own backend servers and also from managed Cloud environments like Google Cloud Functions.
Let’s go through some examples on how to use the Firebase Admin Node.js SDK to manage your projects’ templates. You can start by creating a fresh Remote Config template for your project, or you can get the current active template and make changes to it. Here's how you can get the current active template in Node.js:
const admin = require('firebase-admin'); admin.initializeApp(); const template = await admin.remoteConfig().getTemplate(); console.log('ETag from server: ' + template.etag);
Each Remote Config template version is associated with an entity tag (ETag) for version control purposes. The Remote Config API uses this ETag to prevent race conditions and concurrent updates to resources. To learn more about ETags, see ETag - HTTP.
Once you have obtained your current active Remote Config template you can start making modifications. Here are some of the operations you can perform using the Admin Node.js SDK:
Let’s go through an example on adding a new Remote Config condition and a parameter to your template.
// add a new condition template.conditions.push({ name: 'android_en', expression: "device.os == 'android' " + "&& device.country in ['us','uk']", tagColor: 'BLUE' as admin.remoteConfig.TagColor, }); // add a new parameter, that references the above condition template.parameters['header_text'] = { defaultValue: { value: 'A Gryffindor must be brave, talented and helpful.' }, conditionalValues: { 'android_en': { value: 'A Droid must be brave, talented and helpful.' }, }, };
The above example creates a new condition named android_en and uses it in a conditional value of the parameter named header_text. Checkout the documentation to learn more about conditional expressions. Keep in mind that the conditions in a template are listed in descending order by priority. A parameter might have several conditional values associated with it. If multiple conditions evaluate to true, the first condition in the list takes precedence.
android_en
header_text
Once you make updates to your template you must publish it to make the new changes effective. After you publish a Remote Config template using the Firebase Admin Node.js SDK, that template becomes the current active version of your project. Before you publish a template you can always validate it to check for any errors.
try { const validatedTemplate = admin.remoteConfig() .validateTemplate(template); const publishedTemplate = admin.remoteConfig() .publishTemplate(validatedTemplate); } catch(err) { console.log('Error publishing the template:', error); }
For more details and code samples on programmatically modifying Remote Config templates, check out the detailed documentation.
In addition to the API for modifying Remote Config templates, the Firebase Admin Node.js SDK provides an API to manage Remote Config template versions. This API supports the following operations.
The listVersions operation allows you to retrieve a list of metadata for all stored versions of your project, sorted in reverse chronological order. Note that the last 300 versions are stored. All versions that correspond to non-active Remote Config templates (that is, all except the template that is being fetched by clients) are also deleted if they are more than 90 days old. Here’s a sample code to fetch a list of template versions.
listVersions
// list all the Remote Config template Versions // Only the last 300 versions are stored. const listVersionsResult = admin.remoteConfig().listVersions(); listVersionsResult.versions.forEach((version) => { console.log('version', JSON.stringify(version)); });
For a complete list of available operations and examples, check out the documentation on programmatically managing Remote Config template versions.
We hope this new addition of the Remote Config API in the Admin Node.js SDK helps with your backend development tasks. Stay tuned for the Remote Config API to be added across other Firebase Admin SDK platforms. We are excited to see how you use the new APIs in your projects! Hop over to our Github Repo to check out the implementation. Help us further improve our SDKs by providing your valuable feedback, reporting issues, and contributions to our codebase.
Most apps that you build with Firebase’s backend services, such as Realtime Database, Cloud Firestore, and Cloud Storage, need some way to sign users in: among other things, this lets you provide a consistent experience across sessions and devices, and lets you set user-specific permissions. Firebase Authentication helps you meet this requirement by providing libraries and services that you can use to quickly build a new sign-in system for your app.
But what if your organization already uses a service such as Okta to handle user identity? With Firebase Custom Authentication, you can use any user identity service (including Okta) to authenticate with Firebase, and this post will show you how.
You’ll learn how to build a Firebase and Okta integration, which will have two components:
By the way, this approach can also be used with some modification for other identity services, such as Auth0, Azure Active Directory, or your own custom system.
Ready to get started? Great! But, before you write any code, you’ll need to set up your Okta and Firebase projects.
First, set up an Okta project on the Okta Developer site:
Set the Base URIs and Login redirect URIs to the location where you plan to host your web frontend (http://localhost:5000 if you’re using the Firebase Hosting emulator) and enable the Authorization Code grant type.
http://localhost:5000
When you’re done, take note of the app's Client ID for later.
Then, set up a Firebase project in the Firebase console:
If you plan to eventually host your web app with Firebase, you can automatically set up Firebase Hosting and simplify configuration by enabling Also set up Firebase Hosting for this app.
Finally, if you plan to deploy your token exchange endpoint as a Cloud Function:
Now that your projects are set up, you’ll write the crucial piece: the token exchange endpoint.
The job of the token exchange endpoint is to take a user’s Okta access token and, if it’s valid, produce a Firebase custom authentication token that represents the same user.
This endpoint needs to be able to verify the authenticity of the Okta access token. To accomplish this, use the Express.js middleware provided in Okta’s developer documentation (reproduced below, with minor modifications):
const OKTA_ORG_URL = // Your Okta org URL const OktaJwtVerifier = require('@okta/jwt-verifier'); const oktaJwtVerifier = new OktaJwtVerifier({ issuer: `${OKTA_ORG_URL}/oauth2/default` }); // Middleware to authenticate requests with an Okta access token. const oktaAuth = async (req, res, next) => { const authHeader = req.headers.authorization || ''; const match = authHeader.match(/Bearer (.+)/); if (!match) { res.status(401); return next('Unauthorized'); } const accessToken = match[1]; try { const jwt = await oktaJwtVerifier.verifyAccessToken( accessToken, 'api://default'); req.jwt = jwt; return next(); // Pass the request on to the main route. } catch (err) { console.log(err.message); res.status(401); return next('Unauthorized'); } }
Any endpoint protected by this middleware will require a valid Okta access token in the Authorization header. If the token is valid, it will insert the decoded token into the request before passing the request along by calling next().
Authorization
next()
Now, you can write the token exchange endpoint:
const express = require('express'); const app = express(); const cors = require('cors')({origin: 'https://YOUR_DOMAIN'}); const firebaseAdmin = require('firebase-admin'); const firebaseApp = firebaseAdmin.initializeApp(); // Get a Firebase custom auth token for the authenticated Okta user. // This endpoint uses the `oktaAuth` middleware defined above to // ensure requests have a valid Okta access token. app.get('/firebaseCustomToken', [cors, oktaAuth], async (req, res) => { const oktaUid = req.jwt.claims.uid; try { const firebaseToken = await firebaseApp.auth().createCustomToken(oktaUid); res.send(firebaseToken); } catch (err) { console.log(err.message); res.status(500).send('Error minting token.'); } });
This endpoint uses the Firebase Admin SDK to mint a Firebase custom authentication token using the user’s Okta UID. When you sign a user in with this token for the first time (on the frontend), Firebase Authentication will add a user record with the same UID to your project.
This process of using an Okta access token to acquire a Firebase custom token is the key idea behind integrating Okta and Firebase. But, let’s go one step further and write a simple web frontend to demonstrate the use of the endpoint.
The demo frontend is a plain HTML and JavaScript web app that uses the Firebase Authentication Web SDK and Okta’s sign-in widget library.
Start with two containers: one for authenticated user content and one for Okta’s sign-in widget:
<div id="authenticated-user-content" hidden> <h2>Authenticated with Firebase</h2> <p id="user-info"></p> <button onclick="firebase.auth().signOut();">Sign out</button> </div> <div id="signin-widget" hidden></div>
Set up a Firebase authentication state listener that shows some user profile information to signed-in users and Okta’s sign-in widget to signed-out users:
const oktaSignIn = new OktaSignIn({ baseUrl: OKTA_ORG_URL, redirectUri: window.location.url, authParams: { display: 'page', }, el: '#signin-widget', }); firebase.auth().onAuthStateChanged((user) => { if (user) { // User is signed in. Display some user profile information. document.getElementById('user-info').innerHTML = `Hi, ${user.displayName}! Your email address is ${user.email} and your UID is ${user.uid}.`; document.getElementById('authenticated-user-content').hidden = false; document.getElementById('signin-widget').hidden = true; } else { // User is signed out. Display the Okta sign-in widget. oktaSignIn.showSignInToGetTokens({ clientId: OKTA_CLIENT_ID, redirectUri: window.location.url, getAccessToken: true, getIdToken: true, scope: 'openid profile email', }); document.getElementById('authenticated-user-content').hidden = true; document.getElementById('signin-widget').hidden = false; } });
When a user signs in with Okta’s widget, their browser briefly redirects to Okta’s authorization server, and then, assuming the user signed in successfully, redirects back to your app with the response.
Use Okta’s sign-in library to get the Okta access token from the response and use the access token to get a Firebase custom token from your token exchange endpoint:
if (oktaSignIn.hasTokensInUrl()) { // Get the access token from Okta. const oktaTokenResponse = await oktaSignIn.authClient.token.parseFromUrl(); const accessToken = oktaTokenResponse.tokens.accessToken.value; // Use the access token to call the firebaseCustomToken endpoint. const firebaseTokenResponse = await fetch(CUSTOM_TOKEN_ENDPOINT, { headers: { 'Authorization': `Bearer ${accessToken}`, } }); const firebaseToken = await firebaseTokenResponse.text(); // (Continued below.) }
And finally, authenticate with Firebase using the custom token:
// (Continued from above.) try { await firebase.auth().signInWithCustomToken(firebaseToken); } catch (err) { console.error('Error signing in with custom token.'); }
When the call to signInWithCustomToken() completes, the auth state listener will detect the change and display the user’s profile information.
signInWithCustomToken()
At this point, the user is authenticated with Firebase and you can use any of Firebase’s authentication-enabled services, such as Realtime Database, Cloud Firestore, and Cloud Storage. See the Security Rules documentation for more information on granting resource access to authenticated users.
For the complete demo app and backend that the code snippets above came from, see the Authenticate with Firebase using Okta sample on GitHub.