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 ロールアウトをより詳細に制御する方法の 1 つは、Firebase Remote Config を使用して、アプリの一部のユーザーに対して一度に App Attest を使用した App Check を有効にすることです。これは、構成証明サーバーからのスロットリングを回避するのに役立つ場合があります。 Google Analytics を使用して、ロールアウトがユーザーに与える影響を観察できます。

学べること

この複数ステップのコードラボでは、Firebase Remote Config を使用してアプリの App Check をロールアウトする方法を学びます。

このコードラボでは、DatabaseExample クイックスタート アプリに基づいた Firebase プロジェクトを使用し、Firebase App Check と統合されています ( 「Apple プラットフォーム用 Firebase App Check コードラボ」を参照)。 DatabaseExample クイックスタート アプリを使用すると、ユーザーはログインし、Firebase Realtime Database の機能を使用して投稿を追加できます。

このコードラボの手順を調整して、独自のアプリをテストすることもできます。

前提条件

必要なもの

  • Xcode 12.5+
  • アプリ認証テストの場合:
    • 新しいアプリ識別子を作成できる Apple 開発者アカウント
    • App Attest 機能が有効になっており、明示的な App ID を持つアプリケーション。プロセスに関するヘルプが必要な場合は、 「アプリ ID の登録」および「アプリ機能の有効化」の記事を参照してください。
    • App AttestをサポートするiOS/iPadOSデバイス
  • Firebase プロジェクト:
  • Remote Config の作成と管理、および Google Analytics の表示権限を持つ、アプリに関連付けられた Firebase プロジェクトへのアクセス

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

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

次の手順を完了するには、Xcode のアプリのFrameworks、Libraries、および Embedded Content セクションFirebaseFirebaseRemoteConfig 、およびFirebaseAnalyticsを追加する必要があります。これを行う方法の例については、 Apple プラットフォームの Firebase アプリ チェック コードラボを参照してください。

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

アナリティクスイベントの追加

Analytics イベントを追加すると、App Check のロールアウトがどの程度成功しているかについてより良い洞察が得られます。分析は、より多くのユーザーに対して App Attest を有効にする必要があるかどうかを判断するのに役立ちます。

2 つの Analytics イベント (成功した場合はAppAttestSuccess 、失敗した場合はAppAttestFailure)をログに記録します。これら 2 つの Analytics イベントは、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 プロバイダーに転送するロジックを実装し、いくつかの Analytics イベントを追加した後、 App Check for Apple Platforms コードラボで作成したMyAppCheckProviderFactory.classを更新する必要があります。このクラスは、シミュレーターの場合はApp Check デバッグ プロバイダーをターゲットにし、それ以外の場合はカスタム プロバイダーをターゲットにします。

Apple プラットフォーム用 Firebase App Check コードラボで作成した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. [パラメータ名 (キー)]フィールドにAppAttestEnabledと入力します。
  3. [データ型]ドロップダウンから、 [ブール値]を選択します。
  4. [デフォルト値]ドロップダウンから、 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 テストによる Firebase Remote Config の実験の作成」チュートリアルに従ってAppAttestEnabledパラメータで実験を構成します。チュートリアル セクション「テスト デバイスでの実験の検証」では、テスト デバイスにさまざまな値を割り当てる方法について説明します。

最後のステップは、Google Analytics を使用して、App Attest のロールアウトの成功を監視することです。

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

Analytics イベント ダッシュボードでロールアウトの成功を測定できます。 AppAttestSuccessイベントとAppAttestFailureイベントを監視します。ダッシュボードにイベントが表示されるまでに最大 24 時間かかる場合があります。あるいは、デバッグを有効にして DebugView を使用すると、デバッグ イベントをより迅速に確認できます。

オプションで、Crashlytics ダッシュボードでクラッシュ率の増加を監視できます。アプリに Crashlytics を追加する方法の詳細については、 「Firebase Crashlytics を使ってみる」を参照してください。

大部分のAppAttestSuccessイベントと少数のAppAttestFailureイベントが表示されたら、Remote Config パラメーターAppAttestEnabledの条件を変更することで、App Attest が有効になっているユーザーの割合を増やすことができるという良い兆候です。

Firebase コンソールでの Analytics イベントの確認

オプション: Google Analytics オーディエンスの活用

AppAttestEnabled Analytics イベントをさらに活用したい場合は、 AppAttestEnabled をtrue に設定してユーザーを追跡するAnalytics オーディエンスを作成できます。

App Attest は iOS 14.0 でリリースされました。ユーザーの中にはこのリリースを使用していない可能性があるため、App Attest の対象外となる場合があります。別の Analytics イベントをログに記録してこれらのユーザーを追跡し、その対象ユーザーを別の構成証明メソッド ( DeviceCheckなど) の対象にすることができます。

オプション: Crashlytics を使用してクラッシュを監視する

ロールアウト中のアプリの安定性をより深く理解するには、 Firebase Crashlyticsを使用してクラッシュと非致命的クラッシュを監視します。

6. おめでとうございます!

Remote Config を使用して App Check を正常に展開しました 🎉

追加のリソース: