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

1. はじめに

Firebase App Check と App Attest を組み合わせて使用することで、バックエンド サービスを保護し、Firebase サービスへのリクエストが正規のアプリからのものであることを確認できます。

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

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

App Attest のロールアウトを使用して App Check をより細かく制御する方法の一つは、Firebase Remote Config を使用して、アプリのユーザーの一部に対して一度に App Check と App Attest を有効にすることです。これにより、構成証明サーバーからのスロットリングを回避できます。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 デベロッパー アカウント
    • App Attest 機能が有効になっている明示的なアプリ ID を持つアプリ。手順についてサポートが必要な場合は、アプリ ID を登録するアプリの機能を有効にするをご覧ください。
    • App Attest をサポートしている iOS / iPadOS デバイス
  • Firebase プロジェクト:
  • アプリに関連付けられた Firebase プロジェクトへのアクセス権(Remote Config を作成および管理する権限、および Google アナリティクスを表示する権限)

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

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

次の手順を完了するには、Xcode でアプリの [Frameworks, Libraries, and Embedded Content] セクションに FirebaseFirebaseRemoteConfigFirebaseAnalytics を追加する必要があります。具体的な方法については、Apple プラットフォーム向け Firebase App Check の Codelab をご覧ください。

  1. ファイル「MyAppCheckProvider」を作成します。これは、AppCheckProvider プロトコルに準拠する NSObject のサブクラスです。
  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 Atest を有効にするかどうかを判断するうえで役立ちます。

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. [Default value] プルダウンから、[false] を選択します。

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

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

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

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

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

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

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

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

デバイスでロールアウトをテストする

アプリコードを変更せずにデバイス上でさまざまな Remote Config フラグの値をテストするには、A/B Testing を使用して Firebase Remote Config テストを作成するチュートリアルに沿って、AppAttestEnabled パラメータでテストを設定します。チュートリアルのセクション「テストデバイスでテストを検証する」では、テストデバイスに異なる値を割り当てる方法について説明しています。

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

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 でリリースされました。一部のユーザーは、このリリースを使用していないため、アプリ認証の対象外となる場合があります。別のアナリティクス イベントを記録してこれらのユーザーをトラッキングし、そのオーディエンスをターゲットにして別の構成証明方法(DeviceCheck など)を適用できます。

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

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

6. 完了

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

関連ドキュメント: