Credential Manager の認証情報転送 API を使用すると、認証情報プロバイダ間でユーザー認証情報を安全に転送できます。このガイドでは、Android の認証情報プロバイダが
によって提供される API と統合する方法について説明します。
androidx.credentials:providerevents ライブラリこの機能は、
パスワード、パスキー、住所情報、カスタム フィールドを、
標準化された FIDO 認証情報交換形式(CXF)を使用してサポートしています。
基本コンセプト
認証情報転送フレームワークを使用すると、Android OS や認証されていないアプリに未加工の認証情報を公開することなく、同じデバイス上でピアツーピアの認証情報転送を行うことができます。
このフレームワークでは、次の 2 つの主要な役割が定義されています。
- エクスポータ(ソース プロバイダ): 現在ユーザー認証情報を保持している認証情報プロバイダ。利用可能なエクスポート可能な
アカウント(
ExportEntry)に関するメタデータをシステムに事前登録し、ユーザーが選択したときに転送 リクエストに応答します。 - インポータ(クライアント プロバイダまたはセットアップ ウィザード): 受信できる認証情報と
拡張機能のタイプを指定して、インポート リクエスト
(
ImportCredentialsRequest)を開始する認証情報プロバイダまたは セットアップ ウィザード。
Android バージョンの互換性
Credentials Transfer API は、Android 8(API レベル 26)以降を搭載したデバイスで動作します。
依存関係を追加する
モジュールの
build.gradle または build.gradle.kts に androidx.credentials:providerevents 依存関係を追加します。
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
必要なクラスをインスタンス化する
必要な ProviderEventsManager のインスタンスを作成します。
val providerEventsManager = ProviderEventsManager.create(context)
エクスポータを実装する
ユーザーがアプリからデバイス上の他の認証情報プロバイダに認証情報をエクスポートできるようにするには、エクスポータのロールを実装します。
エクスポート エントリを登録する
認証情報プロバイダが変更された場合(ユーザーがログインした、認証情報を追加した、アカウントを変更したなど)、ProviderEventsManager.registerExport() を使用して ExportEntry アイテムを登録または更新します。
各 ExportEntry には次のものが必要です。
id: このエクスポート エントリを一意に表す、ランダムに生成されたシークレット文字列 ID。この ID は安全に保持する必要があります。後で受信した転送リクエストを確認するために必要になります 。accountDisplayName: 省略可能なアカウント ラベル(例:"Personal Account")。userDisplayName:ユーザーのプライマリ ID(例:"alice@example.com")。icon: プロバイダまたはアカウントを表すBitmapアイコン(ライブラリによって 32x32 PNG に自動的にスケーリングされます)。supportedCredentialTypes: このエントリが保持するタイプを表すCredentialTypesの文字列定数のセット。
suspend fun registerMyProviderForExport( providerEventsManager: ProviderEventsManager, providerIcon: Bitmap, // Randomly generated and stored in encrypted storage secretEntryId: String ) { val entry = ExportEntry( id = secretEntryId, accountDisplayName = "MyProvider Personal", userDisplayName = "alice@example.com", icon = providerIcon, supportedCredentialTypes = setOf( CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, // Passwords CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, // Passkeys CredentialTypes.CREDENTIAL_TYPE_ADDRESS, CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD ) ) // RegisterExportRequest.create() attaches the default WASM matcher from assets val request = RegisterExportRequest.create(context, listOf(entry)) try { val response = providerEventsManager.registerExport(request) // Registration successful } catch (e: Exception) { // Handle registration exceptions (e.g., RegisterExportProviderConfigurationException) } }
マニフェスト ファイルでエクスポータ アクティビティを宣言する
ユーザーがシステム セレクタ UI で ExportEntry を選択すると、指定された処理 Activity が起動します。必須のインテントのアクションとコンテンツ URI スキーマを使用して、この Activity を宣言します。
<activity
android:name="com.example.CredentialExportActivity"
android:exported="true"
android:label="@string/export_activity_label">
<intent-filter>
// This intent action is required for Credential Manager to invoke this activity
<action android:name="androidx.identitycredentials.action.IMPORT_CREDENTIALS" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
</intent-filter>
</activity>
アクティビティで転送インテントを処理する
CredentialExportActivity で IntentHandler.retrieveProviderImportCredentialsRequest(intent) を使用してリクエストを解析し、呼び出し元のアプリと credId を確認し、必要な生体認証を実行して、FIDO CXF ペイロードを指定されたコンテンツ URI に書き戻します。
class CredentialExportActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 1. Extract the transfer request from the incoming Intent val request: ProviderImportCredentialsRequest? = IntentHandler.retrieveProviderImportCredentialsRequest(intent) if (request == null) { finishWithError() return } // 2. Validate CallingAppInfo and secret `credId` val callingAppPackage = request.callingAppInfo.packageName val receivedCredId = request.credId if (!verifySecretEntryId(receivedCredId) || !isTrustedImporter(callingAppPackage)) { // Secret ID mismatch or untrusted caller -> abort sendExceptionAndFinish(ImportCredentialsNoExportOptionException("Unauthorized request")) return } // 3. Optional: Prompt user for Biometric / PIN authentication before exporting authenticateUserThenExport(request) } private fun authenticateUserThenExport(request: ProviderImportCredentialsRequest) { // ... Biometric prompt logic ... // Once authenticated, generate the FIDO CXF JSON string matching the requested types val cxfJsonPayload = buildFidoCxfJsonPayload( requestedTypes = request.request.credentialTypes, requestedExtensions = request.request.knownExtensions ) val response = ImportCredentialsResponse(cxfJsonPayload) // 4. Write the JSON payload to the Content URI and set Activity result IntentHandler.setImportCredentialsResponse( context = this, uri = request.uri, intent = intent, response = response ) setResult(Activity.RESULT_OK, intent) finish() } private fun sendExceptionAndFinish(exception: androidx.credentials.providerevents.exception.ImportCredentialsException) { IntentHandler.setImportCredentialsException(intent, exception) setResult(Activity.RESULT_OK, intent) finish() } private fun verifySecretEntryId(credentialId: String): Boolean { // Check if credentialId matches what you stored when calling RegisterExportRequest return credentialId == getStoredSecretEntryId() } private fun isTrustedImporter(packageName: String): Boolean { // Implement any specific allowlisting / caller checks if required return true } private fun finishWithError() { setResult(Activity.RESULT_CANCELED) finish() } }
エクスポートの登録とクリアの例外
このモジュールのすべての例外は、
ImportCredentialsException、RegisterExportException、または
ClearExportException のサブクラスです。
RegisterExportProviderConfigurationExceptionまたはClearExportProviderConfigurationException: プロバイダの設定の問題(supportedCredentialTypesのExportEntryが空である、サポートされていない OS 階層で呼び出されたなど)が原因で登録 またはクリアに失敗した場合にスローされます。RegisterExportUnknownErrorExceptionまたはClearExportUnknownErrorException: エクスポート レジストリの更新中に、分類されていないシステム エラーまたはストレージ エラーが発生しました。
インポータを実装する
アプリに認証情報をインポートするには(オンボーディング時やプロバイダ
のインポート時など)、インポータのロールを実装し、
ProviderEventsManager.importCredentials()を呼び出してフローを開始します。
インポート リクエストを作成してフローを開始する
インポータがサポートする CredentialTypes と KnownExtensions を指定します。
suspend fun startCredentialImport( activityContext: Context, providerEventsManager: ProviderEventsManager ) { val importRequest = ImportCredentialsRequest( credentialTypes = setOf( CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, CredentialTypes.CREDENTIAL_TYPE_ADDRESS, CredentialTypes.CREDENTIAL_TYPE_NOTE ), knownExtensions = setOf( KnownExtensions.KNOWN_EXTENSION_SHARED ) ) try { // Launches the system Selector UI; suspends until user selects a provider and completes transfer val response = providerEventsManager.importCredentials(activityContext, importRequest) // 1. Inspect the source exporter's package info val exporterPackageName = response.callingAppInfo.packageName // 2. Parse the FIDO CXF JSON string val cxfJsonString = response.response.responseJson parseAndSaveImportedCredentials(cxfJsonString) } catch (e: ImportCredentialsException) { // Handle specific import exceptions (e.g., ImportCredentialsCancellationException) handleImportFailure(e) } } private fun parseAndSaveImportedCredentials(cxfJsonString: String) { val rootJson = JSONObject(cxfJsonString) // Parse according to FIDO Credential Exchange Format (CXF v1.0) specification: // https://fidoalliance.org/specs/cx/cxf-v1.0-ps-20250814.html } // Helper function to make it compile private fun handleImportFailure(e: ImportCredentialsException) {}
インポート フローの例外
このモジュールのすべての例外は、
ImportCredentialsException、RegisterExportException、または
ClearExportException のサブクラスです。
ImportCredentialsCancellationException: ユーザーがセレクタ UI を閉じたか、エクスポート アクティビティをキャンセルしました(Activity.RESULT_CANCELED)。ImportCredentialsNoExportOptionException: リクエストされた認証情報タイプに一致する登録済みエクスポート エントリがないか、選択したエクスポータが拒否例外をスローしました。ImportCredentialsProviderConfigurationException: 構成エラー (たとえば、credentialTypesがImportCredentialsRequestに設定されていない、権限がないなど)。ImportCredentialsInvalidJsonException: エクスポータが、リクエストの検証に失敗した不正な形式または空の JSON ペイロードを返しました。ImportCredentialsSystemErrorException: インポート中に内部 Android システムまたは Binder 転送エラーが発生しました。ImportCredentialsUnknownCallerException: フレームワークで呼び出し元のアプリを確認できませんでした。ImportCredentialsUnknownErrorException: インポート フロー中に分類されていないエラーまたは予期しないエラーが発生しました。
サポートされている認証情報の種類と拡張機能
androidx.credentials.providerevents.transfer.CredentialTypes オブジェクトは、FIDO CXF アイテムタイプに対応する標準化された文字列定数を定義します。
| 定数 | 値(cxf タイプ) |
説明 |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH |
"basic-auth" |
ユーザー名とパスワードのログイン認証情報。 |
CREDENTIAL_TYPE_PUBLIC_KEY |
"passkey" |
FIDO2 または WebAuthn パスキーの公開鍵認証情報。 |
CREDENTIAL_TYPE_ADDRESS |
"address" |
フォームの自動入力用の郵便番号または配送先住所情報。 |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
API アクセスキーとトークン。 |
CREDENTIAL_TYPE_CREDIT_CARD |
"credit-card" |
クレジット カードとデビットカードの支払い情報。 |
CREDENTIAL_TYPE_CUSTOM_FIELDS |
"custom-fields" |
カスタム グループまたはユーザー定義フィールド。 |
CREDENTIAL_TYPE_DRIVERS_LICENSE |
"drivers-license" |
運転免許証の詳細。 |
CREDENTIAL_TYPE_FILE |
"file" |
バイナリ ファイルのメタデータとプレースホルダ参照。 |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
マシン生成の安全なパスワード。 |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
国民 ID カード、SSN、TIN、パスポートの参照。 |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
ペイロード内の別のアイテムを指す論理リンク。 |
CREDENTIAL_TYPE_NOTE |
"note" |
ユーザー定義の安全なメモ(UTF-8 文字列)。 |
CREDENTIAL_TYPE_PASSPORT |
"passport" |
パスポートの渡航書類の詳細。 |
CREDENTIAL_TYPE_PERSON_NAME |
"person-name" |
個人の身元と命名の詳細。 |
CREDENTIAL_TYPE_SSH_KEY |
"ssh-key" |
SSH 公開鍵と秘密鍵のペア。 |
CREDENTIAL_TYPE_TOTP |
"totp" |
時間ベースのワンタイム パスワード(2FA)シークレット。 |
CREDENTIAL_TYPE_WIFI |
"wifi" |
Wi-Fi ネットワークの SSID とパスフレーズ。 |
カスタム WASM(WebAssembly)マッチャー(詳細)
デフォルトでは、RegisterExportRequest.create(context, entries) を呼び出すと、ライブラリ アセットからシステム デフォルトの credential_transfer_matcher.wasm がバンドルされます。これにより、supportedCredentialTypes の交差に基づいてエントリがフィルタされます。
認証情報プロバイダに複雑なマッチング ロジック(動的な機能チェックやカスタム フィールドに基づく条件付きフィルタリングなど)が必要な場合は、WASM 認証情報転送 API に一致する独自の WebAssembly モジュールをコンパイルし、未加工のバイト配列を直接渡すことができます。
// Loading a custom WASM matcher val customMatcherBytes = context.assets.open("my_custom_matcher.wasm").use { it.readBytes() } val customRequest = RegisterExportRequest( entries = myEntries, exportMatcher = customMatcherBytes ) providerEventsManager.registerExport(customRequest)