Gradually roll out Firebase App Check using Firebase Remote Config

1. Introduction

You can use Firebase App Check with App Attest to protect your backend services and verify that requests to Firebase services are coming from your authentic app.

It's generally recommended to onboard users gradually to the App Attest service to avoid hitting quota limits. For more information, see Apple's "Preparing to Use the App Attest Service" documentation.

The ability to release app updates incrementally using Apple's App Store Connect feature, as described in "Releasing a version update in phases," can make the App Check rollout smoother. This is a straightforward, simple solution. However, releasing an app version update in stages doesn't allow you to control the rollout or change behavior of existing, updated apps without publishing a new app version.

One way to have more control over your App Check with App Attest rollout is to use Firebase Remote Config to enable App Check with App Attest for a percentage of your app's users at a time. This may help avoid throttling from the attestation servers. Google Analytics can be used to observe the impact of the rollout on users.

What you'll learn

In this multistep codelab, you'll learn how to use Firebase Remote Config to roll out App Check for your app.

This codelab uses a Firebase project based on the DatabaseExample quickstart app and integrated with Firebase App Check, as described in the Firebase App Check for Apple Platforms codelab. The DatabaseExample quickstart app allows users to log in and add posts using the features of Firebase Realtime Database.

You can also adapt the steps in this codelab to test your own app.

Prerequisites

What you'll need

  • Xcode 12.5+
  • For App Attest testing:
    • An Apple Developer account that allows you to create new app identifiers
    • An application with an explicit App ID with App Attest capability enabled. See Register an App ID and Enable app capabilities articles if you need help with the process.
    • An iOS/iPadOS device that supports App Attest
  • Firebase project with:
  • Access to your app's associated Firebase project, with permissions to create and manage Remote Config and to view Google Analytics

2. Create a custom attestation provider

In this step, we'll create a custom provider class to provide a token only when App Attest is enabled. Remote Config relies on a configured Firebase app instance, and the custom provider you implement in this step acts as the placeholder to finish configuration.

To complete the following steps, you'll need to add Firebase, FirebaseRemoteConfig, and FirebaseAnalytics in the Frameworks, Libraries, and Embedded Content section of your app in Xcode. For an example of how to do this, refer to the Firebase App check for Apple platforms codelab.

  1. Create a file "MyAppCheckProvider" that is a subclass of NSObject conforming to the AppCheckProvider protocol.
  2. Include an empty getToken() method that you'll fill out later.

See the following example code for the custom provider class with empty getToken() method.

// MyAppCheckProvider.swift

import Firebase
import FirebaseAnalytics
import FirebaseAppCheck
import FirebaseRemoteConfig

class MyAppCheckProvider: NSObject, AppCheckProvider {
  func getToken(completion handler: @escaping (AppCheckToken?, Error?) -> Void) {}
}

To instantiate AppAttestProvider, you will need to pass an instance of the corresponding FirebaseApp. Create a stored property for it and accept it as an initializer parameter:

// MyAppCheckProvider.swift

import Firebase
import FirebaseAnalytics
import FirebaseAppCheck
import FirebaseRemoteConfig

class MyAppCheckProvider: NSObject, AppCheckProvider {
  // Firebase app instance served by the provider.
  let firebaseApp: FirebaseApp

  // The App Check provider factory should pass the FirebaseApp instance.
  init(app: FirebaseApp) {
    self.firebaseApp = app
    super.init()
  }

  func getToken(completion handler: @escaping (AppCheckToken?, Error?) -> Void) {}
}

Forward the token request to the App Attest provider

Now you have everything to forward the token request to the App Attest provider in your getToken() method.

Note: Learn more about the getToken() method in the FirebaseAppCheck Framework Reference.

Add the following code to your getToken() method:

// MyAppCheckProvider.swift

import Firebase
import FirebaseAnalytics
import FirebaseAppCheck
import FirebaseRemoteConfig

class MyAppCheckProvider: NSObject, AppCheckProvider {
  // Firebase app instance served by the provider.
  let firebaseApp: FirebaseApp

  // The App Check provider factory should pass the FirebaseApp instance.
  init(app: FirebaseApp) {
    self.firebaseApp = app
    super.init()
  }

  private lazy var appAttestProvider = AppAttestProvider(app: firebaseApp)

  func getToken(completion handler: @escaping (AppCheckToken?, Error?) -> Void) {
    // Fetch App Attest flag from Remote Config
    let remoteConfig = RemoteConfig.remoteConfig(app: firebaseApp)
    remoteConfig.fetchAndActivate { remoteConfigStatus, error in
      // Get App Attest flag value
      let appAttestEnabled = remoteConfig.configValue(forKey: "AppAttestEnabled").boolValue

      guard appAttestEnabled else {
        // Skip attestation if App Attest is disabled. Another attestation
        // method like DeviceCheck may be used instead of just skipping.
        handler(nil, MyProviderError.appAttestIsDisabled)
        return
      }

      // Try to obtain an App Attest provider instance and fail if cannot
      guard let appAttestProvider = self.appAttestProvider else {
        handler(nil, MyProviderError.appAttestIsUnavailable)
        return
      }

      // If App Attest is enabled for the app instance, then forward the
      // Firebase App Check token request to the App Attest provider
      appAttestProvider.getToken(completion: handler)
    }
  }
}

enum MyProviderError: Error {
  case appAttestIsDisabled
  case appAttestIsUnavailable
  case unexpected(code: Int)
}

The previous code checks a Remote Config AppAttestEnabled boolean parameter (this Remote Config parameter will be created later in the codelab). If the value is false, the code fails, indicating that App Check is not rolled out on the current device. If the value is true, the code tries to obtain an App Attest provider and fails if it cannot. If these error checks are passed, the code will forward the token request to the App Attest provider.

Add Analytics events

By adding Analytics events, you get better insights into how successful the App Check rollout is. Analytics will help determine whether App Attest should be enabled for a larger audience.

Log two Analytics events: AppAttestSuccess on success, and AppAttestFailure on failure. These two Analytics events can help track the success of your App Check rollout and help you decide if a larger rollout should proceed.

func getToken(completion handler: @escaping (AppCheckToken?, Error?) -> Void) {
  // Fetch Remote Config.
  let remoteConfig = RemoteConfig.remoteConfig(app: firebaseApp)
  remoteConfig.fetchAndActivate { remoteConfigStatus, error in
    // Get App Attest flag value from Remote Config.
    let appAttestEnabled = remoteConfig.configValue(forKey: "AppAttestEnabled").boolValue

    guard appAttestEnabled else {
      // Skip attestation if App Attest is disabled. Another attestation
      // method like DeviceCheck may be used instead of just skipping.
      handler(nil, MyProviderError.appAttestIsDisabled)
      return
    }

    // Try to obtain an App Attest provider instance and fail otherwise.
    guard let appAttestProvider = self.appAttestProvider else {
      handler(nil, MyProviderError.appAttestIsUnavailable)
      return
    }

    // If App Attest is enabled for the app instance, then forward the
    // Firebase App Check token request to the App Attest provider.
    appAttestProvider.getToken { token, error in
      // Log an Analytics event to track attestation success rate.
      let appAttestEvent: String
      if (token != nil && error == nil) {
        appAttestEvent = "AppAttestSuccess"
      } else {
        appAttestEvent = "AppAttestFailure"
      }
      Analytics.logEvent(appAttestEvent, parameters: nil)

      // Pass the result to the handler
      handler(token, error)
    }
  }
}

3. Update the Provider Factory class

After you've implemented the logic to forward the token request to the App Attest provider and added some Analytics events, you need to update MyAppCheckProviderFactory.class which you created in the App Check for Apple Platforms codelab. This class will target the App Check debug provider for simulators, and otherwise target your custom provider.

Edit the following code in the MyAppCheckProviderFactory class you created in the Firebase App Check for Apple platforms codelab:

// MyAppCheckProviderFactory.swift

import Firebase

class MyAppCheckProviderFactory: NSObject, AppCheckProviderFactory {
  func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
      #if targetEnvironment(simulator)
      // App Attest is not available on simulators.
      // Use a debug provider.
      let provider = AppCheckDebugProvider(app: app)

      // Print only locally generated token to avoid a valid token leak on CI.
      print("Firebase App Check debug token: \(provider?.localDebugToken() ?? "" )")

      return provider
      #else
      if #available(iOS 14.0, *) {
        // Use your custom App Attest provider on real devices.
        return MyAppCheckProvider(app: app)
      } else {
        return DeviceCheckProvider(app: app)
      }
      #endif
  }
}

Confirm you've set the AppCheckProviderFactory before configuring FirebaseApp:

// DatabaseExampleApp.swift

import SwiftUI
import Firebase
import FirebaseAppCheck

@main
struct DatabaseExampleApp: App {
  init() {
    AppCheck.setAppCheckProviderFactory(MyAppCheckProviderFactory())
    FirebaseApp.configure()
  }

  // ...
}

4. Add a Remote Config parameter in the Firebase console

You will now add the Remote Config parameter AppAttestEnabled to the Firebase console . Your getToken method requires this parameter.

To create a Remote Config parameter in the Firebase console :

  1. Open Remote Config for your project and click Add parameter. If this is your first time using Remote Config, click Create configuration.
  2. In the Parameter name (key) field, enter AppAttestEnabled.
  3. From the Data type drop-down, select Boolean.
  4. From the Default value drop-down, select false.

Creating Remote Config Parameter in the Firebase console

Before clicking Save, create a conditional value for 10% of users:

  1. Click Add new > Conditional value > Create new condition.
  2. In the Name field, enter a condition name.
  3. Under Applies if..., select User in random percentile, <=, and then enter 10 in the % field.
  4. Click Create condition.

Defining a Remote Config condition in the Firebase console

Set the conditional value to true so that App Attest will roll out to 10% of your users.

  1. Set the value to true for the condition you just created.
  2. Click Save.

Reviewing Remote Config parameter in the Firebase console

When you're done, publish the Remote Config changes.

Test the rollout on your device

To test the different Remote Config flag values on your device without modifying the app code, configure an experiment on the AppAttestEnabled parameter following the Create Firebase Remote Config Experiments with A/B Testing tutorial. The tutorial section "Validate your experiment on a test device" explains how to assign different values for your test device.

The final step is to use Google Analytics to monitor the success of your App Attest rollout.

5. Review the success of your AppCheck rollout

You can measure the success of your rollout on the Analytics Events dashboard. Watch for AppAttestSuccess and AppAttestFailure events. It may take up to 24 hours to see events in the dashboard. Alternatively, you can enabled debugging and use DebugView to see debug events more quickly.

Optionally, you can monitor the Crashlytics dashboard for increases in crash rates. For more information about adding Crashlytics to your app, see Get started with Firebase Crashlytics.

Once you see mostly AppAttestSuccess events and few AppAttestFailure events, it is a good sign that you can increase the percentage of users with App Attest enabled by modifying the condition in the Remote Config parameter AppAttestEnabled.

Reviewing Analytics events in the Firebase console

Optional: Leverage Google Analytics Audience

If you want to further leverage the AppAttestEnabled Analytics event, you can create an Analytics Audience to track users with AppAttestEnabled set to true.

App Attest was released with iOS 14.0. Some of your users may not be on this release, and thus not eligible for App Attest. You can log another Analytics event to track these users, then target that audience for another attestation method, such as DeviceCheck.

Optional: Use Crashlytics to monitor crashes

To better understand the stability of your app during rollout, use Firebase Crashlytics to monitor crashes and non-fatals.

6. Congratulations!

You successfully rolled out App Check with Remote Config 🎉

Additional resources: