Credential Transfer API Pengelola Kredensial memungkinkan transfer kredensial pengguna yang aman dan di perangkat yang sama antar-penyedia kredensial. Panduan ini menjelaskan
cara penyedia kredensial di Android dapat berintegrasi dengan API yang disediakan oleh
androidx.credentials:providerevents library. Fitur ini mendukung
sandi, kunci sandi, informasi alamat, dan kolom kustom menggunakan
Format Pertukaran Kredensial FIDO (CXF)standar.
Konsep inti
Framework transfer kredensial memfasilitasi transfer kredensial peer-to-peer di perangkat yang sama tanpa mengekspos kredensial mentah ke OS Android atau aplikasi yang tidak diautentikasi.
Framework ini menentukan dua peran utama:
- Pengekspor (penyedia sumber): Penyedia kredensial yang saat ini menyimpan kredensial pengguna. Pengekspor ini melakukan pra-pendaftaran metadata tentang akun yang dapat diekspor
yang tersedia (
ExportEntry) dengan sistem dan merespons permintaan transfer saat dipilih oleh pengguna. - Pengimpor (penyedia klien atau wizard penyiapan): Penyedia kredensial atau
wizard penyiapan yang memulai permintaan impor
(
ImportCredentialsRequest) yang menentukan jenis kredensial dan ekstensi yang dapat diterimanya.
Kompatibilitas versi Android
Credential Transfer API berfungsi di perangkat yang menjalankan Android 8 (level API 26) dan yang lebih baru.
Menambahkan dependensi
Tambahkan dependensi androidx.credentials:providerevents ke
build.gradle atau build.gradle.kts modul Anda:
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
Membuat instance class yang diperlukan
Buat instance ProviderEventsManager yang diperlukan.
val providerEventsManager = ProviderEventsManager.create(context)
Mengimplementasikan pengekspor
Untuk mengizinkan pengguna mengekspor kredensial dari aplikasi Anda ke penyedia kredensial lain di perangkat, implementasikan peran pengekspor.
Mendaftarkan entri ekspor
Saat penyedia kredensial Anda berubah (misalnya, pengguna login, menambahkan kredensial, atau mengubah akun), daftarkan atau perbarui item ExportEntry Anda menggunakan ProviderEventsManager.registerExport().
Setiap ExportEntry memerlukan:
id: ID string rahasia yang dibuat secara acak yang secara unik mewakili entri ekspor ini. Anda harus mempertahankan ID ini dengan aman; Anda akan memerlukannya nanti untuk memverifikasi permintaan transfer yang masuk.accountDisplayName: Label akun opsional (misalnya,"Personal Account").userDisplayName: ID utama pengguna (misalnya,"alice@example.com").icon: IkonBitmapyang mewakili penyedia atau akun (otomatis diskalakan ke PNG 32x32 oleh library).supportedCredentialTypes: Kumpulan konstanta string dariCredentialTypesyang mewakili jenis yang dimiliki entri ini.
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) } }
Mendeklarasikan aktivitas pengekspor dalam file manifes
Saat pengguna memilih ExportEntry Anda di UI pemilih sistem, sistem akan meluncurkan Activity penanganan yang ditentukan. Deklarasikan aktivitas ini dengan tindakan intent dan skema URI konten wajib:
<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>
Menangani intent transfer dalam aktivitas Anda
Di CredentialExportActivity, gunakan IntentHandler.retrieveProviderImportCredentialsRequest(intent) untuk mengurai permintaan, memverifikasi aplikasi pemanggil dan credId, melakukan autentikasi biometrik yang diperlukan, dan menulis payload CXF FIDO kembali ke URI konten yang disediakan:
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() } }
Pengecualian pendaftaran dan penghapusan ekspor
Semua pengecualian dalam modul ini adalah subclass dari
ImportCredentialsException, RegisterExportException, atau
ClearExportException.
RegisterExportProviderConfigurationExceptionatauClearExportProviderConfigurationException: Ditampilkan saat pendaftaran atau penghapusan gagal karena masalah penyiapan penyedia (misalnya,supportedCredentialTypeskosong diExportEntry, atau memanggil di tingkat OS yang tidak didukung).RegisterExportUnknownErrorExceptionatauClearExportUnknownErrorException: Error sistem atau penyimpanan yang tidak diklasifikasikan terjadi saat memperbarui registry ekspor.
Mengimplementasikan pengimpor
Untuk mengimpor kredensial ke aplikasi Anda (misalnya, selama orientasi atau impor penyedia
), implementasikan peran pengimpor dan mulai alur dengan memanggil
ProviderEventsManager.importCredentials().
Membuat permintaan impor dan meluncurkan alur
Tentukan CredentialTypes dan KnownExtensions yang didukung pengimpor Anda:
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) {}
Pengecualian alur impor
Semua pengecualian dalam modul ini adalah subclass dari
ImportCredentialsException, RegisterExportException, atau
ClearExportException.
ImportCredentialsCancellationException: Pengguna menutup UI pemilih atau membatalkan aktivitas ekspor (Activity.RESULT_CANCELED).ImportCredentialsNoExportOptionException: Tidak ada entri ekspor terdaftar yang cocok dengan jenis kredensial yang diminta, atau pengekspor yang dipilih menampilkan pengecualian penolakan.ImportCredentialsProviderConfigurationException: Error konfigurasi (misalnya,credentialTypeskosong yang ditetapkan diImportCredentialsRequestatau izin yang tidak ada).ImportCredentialsInvalidJsonException: Pengekspor menampilkan payload JSON yang salah format atau kosong yang gagal dalam validasi permintaan.ImportCredentialsSystemErrorException: Error transfer sistem Android atau Binder internal terjadi selama impor.ImportCredentialsUnknownCallerException: Aplikasi pemanggil tidak dapat diverifikasi oleh framework.ImportCredentialsUnknownErrorException: Error yang tidak diklasifikasikan atau tidak terduga selama alur impor.
Jenis dan ekstensi kredensial yang didukung
Objek androidx.credentials.providerevents.transfer.CredentialTypes menentukan konstanta string standar yang sesuai dengan jenis item CXF FIDO:
| Konstanta | Nilai (jenis cxf) |
Deskripsi |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH |
"basic-auth" |
Kredensial login nama pengguna dan sandi. |
CREDENTIAL_TYPE_PUBLIC_KEY |
"passkey" |
Kredensial kunci publik kunci sandi FIDO2 atau WebAuthn. |
CREDENTIAL_TYPE_ADDRESS |
"address" |
Info alamat pengiriman atau pos untuk pengisian otomatis formulir. |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
Kunci dan token akses API. |
CREDENTIAL_TYPE_CREDIT_CARD |
"credit-card" |
Informasi pembayaran kartu kredit dan debit. |
CREDENTIAL_TYPE_CUSTOM_FIELDS |
"custom-fields" |
Pengelompokan kustom atau kolom yang ditentukan pengguna. |
CREDENTIAL_TYPE_DRIVERS_LICENSE |
"drivers-license" |
Detail surat izin mengemudi. |
CREDENTIAL_TYPE_FILE |
"file" |
Metadata dan referensi placeholder untuk file biner. |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
Sandi aman yang dibuat oleh mesin. |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
Referensi KTP, SSN, TIN, atau paspor. |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
Link logis yang mengarah ke item lain dalam payload. |
CREDENTIAL_TYPE_NOTE |
"note" |
Catatan aman yang ditentukan pengguna (string UTF-8). |
CREDENTIAL_TYPE_PASSPORT |
"passport" |
Detail dokumen perjalanan paspor. |
CREDENTIAL_TYPE_PERSON_NAME |
"person-name" |
Detail identitas dan penamaan orang. |
CREDENTIAL_TYPE_SSH_KEY |
"ssh-key" |
Pasangan kunci publik dan pribadi SSH. |
CREDENTIAL_TYPE_TOTP |
"totp" |
Secret sandi sekali pakai berbasis waktu (2FA). |
CREDENTIAL_TYPE_WIFI |
"wifi" |
SSID dan frasa sandi jaringan Wi-Fi. |
Pencocok WASM (WebAssembly) kustom (Lanjutan)
Secara default, memanggil RegisterExportRequest.create(context, entries) akan memaketkan credential_transfer_matcher.wasm default sistem dari aset library, yang memfilter entri hanya berdasarkan perpotongan supportedCredentialTypes.
Jika penyedia kredensial Anda memerlukan logika pencocokan yang kompleks (misalnya, pemeriksaan kemampuan dinamis atau pemfilteran bersyarat berdasarkan kolom kustom), Anda dapat mengompilasi modul WebAssembly Anda sendiri yang cocok dengan WASM credential transfer API dan meneruskan array byte mentah secara langsung:
// 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)