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 では、DatabaseExample クイックスタート アプリに基づく Firebase プロジェクトを使用し、Apple プラットフォーム用の Firebase App Check Codelab で説明されているように、Firebase App Check と統合します。DatabaseExample クイックスタート アプリでは、Firebase Realtime Database の機能を使用してログインしたり、投稿を追加したりできます。

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

前提とする環境

必要なもの

  • Xcode 12.5 以降
  • App Attest テストの場合:
    • 新しいアプリ識別子を作成できる 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. [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 アナリティクスを使用して、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 をロールアウトしました 🎉?

関連ドキュメント: