Firebase Remote Config を使用して Firebase App Check を段階的にロールアウトする

1. はじめに

App Attest で Firebase App Check を使用すると、バックエンド サービスを保護し、Firebase サービスへのリクエストが本物のアプリから送信されたものであることを確認できます。

通常は、割り当て上限に達しないように、ユーザーを App Attest サービスに段階的にオンボーディングすることをおすすめします。詳細については、Apple の App Attest サービスの使用準備のドキュメントをご覧ください。

バージョン アップデートを段階的にリリースするで説明されているように、Apple の App Store Connect 機能を使用してアプリのアップデートを段階的にリリースできるため、App Check のリリースがよりスムーズになります。これはシンプルで明快なソリューションです。ただし、アプリのバージョン アップデートを段階的にリリースしても、新しいアプリ バージョンを公開することなく、更新された既存のアプリの公開や動作変更を制御することはできません。

App Attest のロールアウトで App Check をより詳細に管理する方法の一つは、Firebase Remote Config を使用して、App Attest で一度にアプリのユーザーの一部に対して App Check を有効にすることです。これにより、構成証明サーバーからのスロットリングを回避できます。Google アナリティクスを使用して、このロールアウトがユーザーに与える影響を観察できます。

ラボの内容

この Codelab では、Firebase Remote Config を使用してアプリに App Check をデプロイする方法を学びます。

この Codelab では、Apple プラットフォーム用の Firebase App Check の Codelab で説明されているように、DatabaseExample クイックスタート アプリをベースにした Firebase プロジェクトを使用し、Firebase App Check と統合します。DatabaseExample クイックスタート アプリを使用すると、ユーザーは Firebase Realtime Database の機能を使用してログインして投稿を追加できます。

この Codelab の手順を応用して、独自のアプリをテストすることもできます。

前提とする環境

必要なもの

  • Xcode 12.5 以降
  • App Attest のテストの場合:
    • 新しいアプリ ID を作成できる Apple Developer アカウント
    • App Attest 機能が有効になっている明示的なアプリ ID を持つアプリ。プロセスについてサポートが必要な場合は、アプリ ID を登録するアプリの機能を有効にするの記事をご覧ください。
    • App Attest に対応している iOS または iPadOS デバイス
  • 次のものを含む Firebase プロジェクト:
  • アプリに関連付けられた Firebase プロジェクトへのアクセス権と、Remote Config を作成、管理する権限、および Google アナリティクスを表示する権限

2. カスタム証明書プロバイダを作成する

このステップでは、App Attest が有効な場合にのみトークンを提供するカスタム プロバイダ クラスを作成します。Remote Config は構成済みの Firebase アプリ インスタンスに依存しており、このステップで実装するカスタム プロバイダは構成を完了するためのプレースホルダとして機能します。

以下の手順を完了するには、Xcode のアプリのフレームワーク、ライブラリ、埋め込みコンテンツに、FirebaseFirebaseRemoteConfigFirebaseAnalytics を追加する必要があります。これを行う方法の例については、Apple プラットフォーム向けの Firebase App Check の Codelab をご覧ください。

  1. AppCheckProvider プロトコルに準拠した NSObject のサブクラスである MyAppCheckProvider ファイルを作成します。
  2. 後で入力する空の getToken() メソッドを含めます。

空の getToken() メソッドを持つカスタム プロバイダ クラスについては、次のサンプルコードをご覧ください。

// MyAppCheckProvider.swift

import Firebase
import FirebaseAnalytics
import FirebaseAppCheck
import FirebaseRemoteConfig

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

AppAttestProvider をインスタンス化するには、対応する FirebaseApp のインスタンスを渡す必要があります。その保存済みプロパティを作成し、イニシャライザ パラメータとして受け取ります。

// 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) {}
}

トークン リクエストを App Attest プロバイダに転送する

これで、getToken() メソッドでトークン リクエストを App Attest プロバイダに転送するための準備が整いました。

注: getToken() メソッドについて詳しくは、FirebaseAppCheck フレームワーク リファレンスをご覧ください。

getToken() メソッドに次のコードを追加します。

// 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)
}

上記のコードは、Remote Config の AppAttestEnabled ブール値パラメータをチェックします(この Remote Config パラメータは、この Codelab の後半で作成します)。この値が false の場合、コードは失敗し、現在のデバイスに App Check がロールアウトされていないことを示します。この値が true の場合、コードは App Attest プロバイダを取得しようとし、取得できない場合は失敗します。これらのエラーチェックに合格すると、コードがトークン リクエストを App Attest プロバイダに転送します。

アナリティクス イベントを追加する

アナリティクス イベントを追加することで、App Check のロールアウトがどの程度成功したかについて、より詳細なインサイトを得ることができます。アナリティクスは、より多くのオーディエンスに対して App Attest を有効にするかどうかを判断するのに役立ちます。

2 つのアナリティクス イベントをロギングします。成功の場合は AppAttestSuccess、失敗した場合は AppAttestFailure です。この 2 つのアナリティクス イベントは、App Check のロールアウトの成功を追跡し、より大規模なロールアウトを行うべきかどうかを判断するのに役立ちます。

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. Provider Factory クラスを更新する

トークン リクエストを App Attest プロバイダに転送するロジックを実装し、アナリティクス イベントをいくつか追加したら、Apple プラットフォーム向け App Check の Codelab で作成した MyAppCheckProviderFactory.class を更新する必要があります。このクラスは、シミュレータでは App Check デバッグ プロバイダをターゲットにし、それ以外の場合はカスタム プロバイダをターゲットにします。

Apple プラットフォーム用の Firebase App Check の Codelab で作成した MyAppCheckProviderFactory クラスの次のコードを編集します。

// 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
  }
}

FirebaseApp を構成する前に、AppCheckProviderFactory が設定されていることを確認します。

// DatabaseExampleApp.swift

import SwiftUI
import Firebase
import FirebaseAppCheck

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

  // ...
}

4. Firebase コンソールで Remote Config パラメータを追加する

次に、Remote Config パラメータ AppAttestEnabled を Firebase コンソールに追加します。getToken メソッドにはこのパラメータが必要です。

Firebase コンソールで Remote Config パラメータを作成するには :

  1. プロジェクトの Remote Config を開き、[パラメータを追加] をクリックします。Remote Config を初めて使用する場合は、[構成を作成] をクリックします。
  2. [Parameter name (key)] フィールドに「AppAttestEnabled」と入力します。
  3. [データ型] プルダウンから [ブール値] を選択します。
  4. [デフォルト値] プルダウンから [false] を選択します。

Firebase コンソールで Remote Config パラメータを作成する

[保存] をクリックする前に、10% のユーザーを対象とする条件値を作成します。

  1. [新たに追加] > [条件値] > [新しい条件を作成] をクリックします。
  2. [名前] フィールドに、条件名を入力します。
  3. [次の場合に適用する...] で、[ユーザー(ランダム パーセンタイルのユーザー)]、[<=] を選択し、[%] フィールドに「10」と入力します。
  4. [条件を作成] をクリックします。

Firebase コンソールで Remote Config の条件を定義する

条件値を true に設定すると、App Attest がユーザーの 10% にロールアウトされます。

  1. 作成した条件の値を true に設定します。
  2. [保存] をクリックします。

Firebase コンソールで Remote Config パラメータを確認する

完了したら、Remote Config の変更を公開します。

デバイスで公開をテストする

アプリコードを変更せずにデバイスでさまざまな Remote Config フラグの値をテストするには、A/B Testing で Firebase Remote Config テストを作成するチュートリアルに沿って、AppAttestEnabled パラメータでテストを構成します。テストデバイスに異なる値を割り当てる方法については、チュートリアルのテストデバイスでテストを検証するをご覧ください。

最後のステップでは、Google アナリティクスを使用して App Attest のロールアウトの成功をモニタリングします。

5. AppCheck のロールアウトの成功を確認する

ロールアウトの成功は、アナリティクスのイベント ダッシュボードで測定できます。AppAttestSuccess イベントと AppAttestFailure イベントに注意してください。ダッシュボードにイベントが表示されるまで、最長で 24 時間かかる場合があります。または、デバッグを有効にして DebugView を使用すると、デバッグ イベントをより迅速に表示できます。

必要に応じて、Crashlytics ダッシュボードでクラッシュ発生率の増加をモニタリングできます。アプリに Crashlytics を追加する方法について詳しくは、Firebase Crashlytics を使ってみるをご覧ください。

主に AppAttestSuccess イベントが表示され、AppAttestFailure イベントがほとんど表示されない場合は、Remote Config パラメータ AppAttestEnabled の条件を変更することで、App Attest が有効になっているユーザーの割合を増やすことができることを示しています。

Firebase コンソールでアナリティクス イベントを確認する

省略可: Google アナリティクスのオーディエンスを活用する

AppAttestEnabled アナリティクス イベントをさらに活用するには、アナリティクスのオーディエンスを作成し、AppAttestEnabled を true に設定してユーザーを追跡します。

App Attest は iOS 14.0 でリリースされました。一部のユーザーがこのリリースに含まれないため、App Attest の対象外となる可能性があります。別のアナリティクス イベントをロギングしてこれらのユーザーをトラッキングし、そのオーディエンスを DeviceCheck などの別の認証方法のターゲットに設定することもできます。

省略可: Crashlytics を使用してクラッシュをモニタリングする

ロールアウト中のアプリの安定性を詳しく把握するには、Firebase Crashlytics を使用して、クラッシュと非致命的なエラーをモニタリングします。

6. お疲れさまでした

Remote Config を使用して App Check が正常にロールアウトされました 🎉?

関連ドキュメント: