認証情報マネージャーで認証情報の復元を実装する

このページでは、復元キーの作成、復元キーを使用したログイン、復元キーの削除を行う方法について説明します。

バージョンの互換性

認証情報マネージャーの復元用認証情報は、Android 9(API レベル 28)以降、Google Play 開発者サービス(GMS)コア バージョン 242200000(v24.22)以降、androidx.credentials ライブラリのバージョン 1.5.0 以降を搭載したデバイスで動作します。Google Play 開発者サービスが搭載されていないデバイスや、古い GMS バージョンが搭載されているデバイスでは、CreateCredentialProviderConfigurationException と GetCredentialProviderConfigurationException をキャッチして、アプリが標準のログインに適切にフォールバックするようにします。

前提条件

パスキーのサーバーと同様に、WebAuthn 証明書利用者サーバーを構成します。パスキーによる認証を処理するようにサーバーがすでに設定されている場合は、同じ WebAuthn 登録エンドポイントとアサーション エンドポイントを復元キーに再利用できます。

依存関係

アプリ モジュールの build.gradle ファイルに次の依存関係を追加します。

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha02")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha02"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02"
}

Restore Credentials は、androidx.credentials ライブラリのバージョン 1.5.0 以降で利用できます。ただし、可能な限り、依存関係の最新の安定版を使用することをおすすめします。

android:allowBackup が Restore Credentials に与える影響

android:allowBackup マニフェスト属性は、アプリデータのバックアップを制御します。この属性は、次の 3 つの方法で Restore Credentials とやり取りします。

  • 復元鍵の転送(allowBackup に依存しない): Google Play 開発者サービスは、android:allowBackup が true か false かに関係なく、エンドツーエンドの暗号化されたクラウド バックアップまたはローカルのデバイス間(D2D)転送を使用して RestoreCredential 鍵ペアを転送します。D2D 移行の場合、復元キーは常に移行されます。移行されない唯一の条件は、ユーザーが Android の設定でクラウド バックアップを無効にしている場合のクラウド バックアップ時です。
  • BackupAgent でのバックグラウンド復元(android:allowBackup="true" が必要): ユーザーが新しいデバイスでアプリを開く前のサイレント バックグラウンド ログイン(BackupAgent.onRestoreFinished())は、マニフェストで android:allowBackup="true" と android:backupAgent が構成されている場合にのみ実行されます。android:allowBackup="false" の場合は、ユーザーがメインの Activity を初めて起動したときにセッションを復元します。両方のアプローチを実装して、復元をより堅牢にすることもできます。
  • Android Studio のクラウド バックアップ テスト(android:allowBackup="true" が必要): Android Studio のバックアップと復元ツールでクラウド バックアップ タイプをテストするには、android:allowBackup="true" が必要です。一方、デバイス間テストは allowBackup に関係なく機能します。

概要

Restore Credentials は、ユーザーの以前のデバイスと新しいデバイスにわたる 3 段階のライフサイクルに従います。

  1. 以前のデバイスで復元キーを作成する: ユーザーがログインした後(または、ユーザーがすでにログインしていて、ローカル ストレージに復元キーが記録されていない場合はアプリの起動時)、証明書利用者サーバーから作成オプションを取得し、credentialManager.createCredential() を呼び出します。
  2. 新しいデバイスで復元キーを使用してログインする: 2 段階の戦略を使用して、転送された RestoreCredential を取得します。まず、BackupAgent.onRestoreFinished()(android:allowBackup="true" の場合)でバックグラウンドで取得し、バックグラウンド同期とプッシュ通知をすぐに再開します。次に、アプリの初回起動時にランチャー Activity.onCreate() でフォールバックとして取得します。
  3. ログアウト時に復元キーを削除する: ユーザーがログアウトするか、サーバーによってセッションが無効化されたら、ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) を指定して credentialManager.clearCredentialState() を呼び出し、ローカル同期フラグをクリアします。

復元キーを作成する

アプリは、ユーザーがログインするすべてのケースをカバーし、アクティブ ユーザーに復元キーが作成されるようにする必要があります。次のシナリオで復元キーを作成します。

  • ユーザーがログインしていて、復元キーがまだ作成されていない場合(メインの Activity の onCreate() メソッドなど)。
  • ユーザーがログインしているとき、または新しいアカウント登録フローを完了しているとき。

アプリを起動するたびに createCredential() が呼び出されないようにするには、has_synced_restore_credential などのローカル同期フラグをローカル ストレージに保存します。このフラグは、デフォルトでバックアップされる SharedPreferences または DataStore に保存しないでください。ただし、そのファイルが data_extraction_rules.xml で除外されている場合は保存してもかまいません。保存すると、自動バックアップによって has_synced_restore_credential = true が新しいデバイスにコピーされ、新しいデバイスで独自の復元キーの生成やローテーションができなくなります。

認証情報マネージャーをインスタンス化する

アプリのアクティビティ コンテキストを使用して、CredentialManager オブジェクトをインスタンス化します。

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

アプリサーバーから認証情報作成オプションを取得する

アプリサーバーで FIDO 準拠のライブラリを使用して、復元認証情報の作成に必要な PublicKeyCredentialCreationOptions JSON をクライアント アプリに送信します。サーバーサイドの実装について詳しくは、サーバーサイドのガイダンスをご覧ください。

作成オプションの JSON は、パスキーの登録と同じ構造に従います。

{
  "challenge": "<base64url-encoded challenge>",
  "rp": {
    "name": "<relying party name>",
    "id": "<relying party host name>"
  },
  "user": {
    "id": "<base64url-encoded user ID>",
    "name": "<user name>",
    "displayName": "<user display name>"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }
  ],
  "attestation": "none",
  "excludeCredentials": [
    {
        "id": "<base64url-encoded credential ID to exclude>", 
        "type": "public-key"
    }
  ],
  "authenticatorSelection": {
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

作成オプションの JSON が次の要件を満たしていることを確認します。

  • challenge と user.id: 両方の値をパディングなしの Base64URL 文字列としてエンコードします(+ を - に、/ を _ に置き換え、末尾の = を削除します)。標準の Base64 文字または未加工の整数 ID を使用すると、CreateRestoreCredentialDomException または IllegalArgumentException が発生します。
  • rp.id: 証明書利用者(RP)のドメイン(例: example.com)に設定します。
  • pubKeyCredParams: {"type": "public-key", "alg": -7}(ES256)と {"type": "public-key", "alg": -257}(RS256)を含めます。

復元キーを作成する

サーバーから公開鍵作成オプションの JSON を受け取ったら、JSON を CreateRestoreCredentialRequest オブジェクトにラップし、CredentialManager オブジェクトで createCredential() を呼び出します。

// createRestoreRequest contains the details sent by the server 
val response = credentialManager.createCredential(context, createRestoreRequest)

コードに関する主なポイント

  • CreateRestoreCredentialRequest オブジェクトには次のフィールドがあります。

    • requestJson: PublicKeyCredentialCreationOptionsJSON の Web Authentication API 形式でアプリサーバーから送信される認証情報作成オプション。
    • isCloudBackupEnabled: Boolean フィールド。復元キーをクラウドにバックアップする必要があるかどうかを判断します。デフォルトでは、このフラグは true です。このフィールドには次の値が設定されます。

      • true: (推奨)この値を使用すると、ユーザーが Google バックアップとエンドツーエンドの暗号化(画面ロックなど)を有効にしている場合に、復元キーのクラウドへのバックアップが有効になります。
      • false: この値は、鍵をクラウドではなくローカルに保存します。ユーザーがクラウドからの復元を選択した場合、鍵は新しいデバイスでは使用できませんが、ローカル ケーブルまたは Wi-Fi デバイス間(D2D)セットアップ中に転送されます。

認証情報の作成レスポンスを処理する

Credential Manager API は、CreateRestoreCredentialResponse タイプのレスポンスを返します。このレスポンスには、JSON 形式の公開鍵認証情報の登録レスポンスが含まれています。

アプリから証明書利用者サーバーに response.registrationResponseJson を送信して、公開鍵を検証し、ユーザーのアカウントとともに保存します。その後、ローカル同期マーカーをローカル ストレージに記録します。サーバーサイド認証の詳細については、パスキーのガイダンスをご覧ください。

復元キーの作成プロセス中に、次の例外を処理します。

  • CreateRestoreCredentialDomException: この例外は、requestJson が無効で、PublicKeyCredentialCreationOptionsJSON の WebAuthn 形式に準拠していない場合に発生します。
  • E2eeUnavailableException: この例外は、isCloudBackupEnabled が true であるにもかかわらず、ユーザーのデバイスにデータ バックアップまたはエンドツーエンドの暗号化(画面ロックなど)がない場合に発生します。
    クラウド E2EE が利用できない場合でも、ローカルのデバイス間(D2D)転送用に復元認証情報が作成されるようにするには、E2eeUnavailableException をキャッチして、CreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false) を使用してすぐに createCredential() を再試行します。
  • IllegalArgumentException: この例外は、requestJson が空の場合、有効な JSON ではない場合、または WebAuthn の仕様に準拠した有効な Base64URL エンコードの user.id がない場合に発生します。

復元キーでログインする

Restore Credentials を使用して、デバイスのセットアップ プロセス中にユーザーをバックグラウンドでログインさせます。

アプリサーバーから認証情報取得オプションを取得する

新しいデバイスで getCredential() を呼び出す前に、アプリサーバーから WebAuthn PublicKeyCredentialRequestOptions JSON チャレンジをリクエストします。新しいデバイス上のアプリは、どのユーザーがログインしているかをまだ認識していないため、このエンドポイントは未認証(ユーザーレス検出)である必要があります。関連するサーバー ガイダンスについては、パスキーでログインするとサーバーサイド認証ガイドをご覧ください。

取得オプションの JSON の構造は次のとおりです。

{
  "challenge": "<your app challenge>",
  "allowCredentials": [],
  "rpId": "<your app server domain>"
}

サーバーで取得オプションの JSON を次のように構成します。

  • allowCredentials: 復元前にユーザーのアカウントが不明なため、このフィールドを省略するか、空の配列([])を渡します。getCredential() が返されると、サーバーは response.userHandle フィールド(作成時に登録された user.id と一致)からユーザーを識別し、id(credentialId)で公開鍵を検索します。
  • userVerification: "discouraged"(または "preferred")に設定します。Restore Credentials は生体認証や画面ロックの UI なしでサイレントに実行されるため、Android は "required" を "discouraged" にオーバーライドし、サーバーに返される認証システム データでは、ユーザー確認済み(uv)フラグが false に設定されます。復元キー アサーションに uv = false を受け入れるように WebAuthn サーバー検証ツールを構成します。

復元キーを取得する

新しいデバイスで復元キーを取得するには、サーバーの requestJson を GetCredentialRequest 内の GetRestoreCredentialOption でラップし、CredentialManager オブジェクトで getCredential() を呼び出します。

2 段階の復元戦略を実装して、バックグラウンドでのアプリデータの復元が実行されるかどうかにかかわらず、ユーザーがログインできるようにします。

  • Tier 1(BackupAgent.onRestoreFinished() でのバックグラウンド復元): アプリデータのバックアップが有効になっている場合(android:allowBackup="true")、BackupAgent をサブクラス化して onRestoreFinished() をオーバーライドします(onRestore() は使用しないでください。これは、以前の Key-Value バックアップでのみ呼び出されます)。onRestoreFinished() は同期的で、getCredential() は Kotlin の suspend 関数であるため、runBlocking(Dispatchers.IO) を使用して呼び出しをブリッジします。復元キーが存在しない場合は、NoCredentialException を静かにキャッチします。
  • Tier 2(ランチャー Activity.onCreate() のフォアグラウンド フォールバック): ローカル セッションが存在しない状態でアプリを初めて起動するときに、ログイン画面を表示する前に lifecycleScope から getCredential() を呼び出します。これは、android:allowBackup="false" を使用するアプリを処理し、onRestoreFinished() の実行時にデバイスがオフラインだった場合に復元します。

android:allowBackup="true" で自動バックアップをすでに使用しているアプリに新しい BackupAgent を追加する場合は、AndroidManifest.xml の <application> 要素に android:fullBackupOnly="true" も設定して、Android が以前の Key-Value バックアップに切り替えるのではなく、自動バックアップを引き続き使用するようにします。

// Fetch the options required to get the restore key
val authenticationJson = fetchAuthenticationJson()

// Create the GetRestoreCredentialRequest object
val options = GetRestoreCredentialOption(authenticationJson)
val getRequest = GetCredentialRequest(listOf(options))

val response = credentialManager.getCredential(context, getRequest)

// Type-check and extract the restore credential
val credential = response.credential as RestoreCredential

Credential Manager API は、credential プロパティが authenticationResponseJson を含む RestoreCredential(PublicKeyCredential ではない)である GetCredentialResponse を返します。

ログイン レスポンスを処理する

アプリから証明書利用者サーバーに restoreCredential.authenticationResponseJson を送信して、暗号アサーションを検証し、ユーザーに新しいセッション トークンを発行します。サーバーサイド認証の詳細については、パスキーでログインするをご覧ください。

復元キーのバックエンド ストレージとライフサイクル ポリシーを設計する際は、次のガイドラインに従ってください。

  • 復元キーをユーザーのパスキーと区別する: データベース内の復元キーにタグを付けて、ユーザー向けのパスキー管理画面に表示されないようにし、ユーザー確認(uv = true)チェックの対象外にします。
  • ユーザーごとに複数のアクティブなデバイスをサポートする: ユーザーは複数のデバイス(スマートフォンやタブレットなど)を所有できます。1 つの行を上書きするのではなく、ユーザー アカウントごとに複数のアクティブな復元キー(デバイス ID やインストール ID をキーとするなど)を許可します。
  • 適切な TTL を設定し、孤立したキーをクリーンアップする: アプリをアンインストールするか、アプリデータを消去すると、サーバーに通知されることなくローカルの復元キーが削除されます。デバイスのアップグレード期間を乗り切るのに十分な期間、復元キーを保持し(ユーザーが移行後に古いデバイスでログアウトした場合でも)、非アクティブ TTL またはユーザーごとのキー制限に基づいて古いキーを削除します。

復元キーを削除する

認証情報マネージャーはステートレスであり、ユーザー アクティビティを認識しないため、使用後に復元キーを自動的に削除することはありません。復元鍵を削除するには、clearCredentialState() メソッドを呼び出します。セキュリティ上の理由から、ユーザーがログアウトするたびに鍵を削除して、次にアプリを起動したときにユーザーが自動的にログインされないようにします。

復元キーは、次の状況でのみ削除されます。

  • システムレベルのアクション: ユーザーがアプリをアンインストールするか、アプリのデータを消去します。
  • アプリレベルの呼び出し: アプリのコードでユーザーのログアウトを処理するときに clearCredentialState() を呼び出して、プログラムで鍵を削除します。

デフォルトの ClearCredentialStateRequest() では、標準のセッション状態のみがクリアされ、復元キーはそのまま残るため、ログアウト時にデュアルクリアを実行します。

  1. credentialManager.clearCredentialState(ClearCredentialStateRequest()) を呼び出して、アクティブな認証情報プロバイダのセッション状態をクリアします。
  2. ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) を指定して credentialManager.clearCredentialState() を呼び出し、保存されている RestoreCredential を削除します。
  3. ローカル ストレージからローカルの has_synced_restore_credential マーカーを削除します。
// Create a ClearCredentialStateRequest object
val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)

// When the user logs out, delete the restore key
val response = credentialManager.clearCredentialState(clearRequest)

次のステップ