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

1. はじめに

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

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

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

App Check with App Attest のロールアウトをより細かく制御する方法の一つとして、Firebase Remote Config を使用して、アプリのユーザーの一定割合に対して App Check with 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 以降
  • アプリ認証のテストの場合:
    • 新しいアプリ 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. 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 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 パラメータを追加する

次に、Firebase コンソールに Remote Config パラメータ AppAttestEnabled を追加します。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 アナリティクスを使用して、アプリ認証のロールアウトの成功をモニタリングすることです。

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

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

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

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

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

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

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

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

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

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

6. 完了

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

その他のリソース: