API Transfer Kredensial Pengelola Kredensial memungkinkan transfer kredensial pengguna yang aman dan di perangkat yang sama antara penyedia kredensial. Panduan ini menjelaskan
cara penyedia kredensial di Android dapat berintegrasi dengan API yang disediakan oleh
library androidx.credentials:providerevents. Fitur ini mendukung
sandi, kunci sandi, informasi alamat, dan kolom kustom menggunakan
Format Pertukaran Kredensial FIDO (CXF) yang standar.
Konsep inti
Framework transfer kredensial memfasilitasi transfer kredensial peer-to-peer di perangkat yang sama tanpa mengekspos kredensial mentah ke Android OS atau aplikasi yang tidak diautentikasi.
Framework ini menentukan dua peran utama:
- Pengekspor (penyedia sumber): Penyedia kredensial yang saat ini memegang
kredensial pengguna. API ini melakukan pra-pendaftaran metadata tentang akun yang tersedia dan dapat diekspor (
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
Credentials 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")
}
Buat 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, terapkan 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 dan secara unik merepresentasikan entri ekspor ini. Anda harus menyimpan ID ini dengan aman; Anda akan memerlukannya nanti untuk memverifikasi permintaan transfer masuk.accountDisplayName: Label akun opsional (misalnya,"Personal Account").userDisplayName: ID utama pengguna (misalnya,"alice@example.com").icon: IkonBitmapyang merepresentasikan penyedia atau akun (secara otomatis disesuaikan skalanya menjadi PNG 32x32 oleh library).supportedCredentialTypes: Kumpulan konstanta string dariCredentialTypesyang merepresentasikan 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 Anda tetapkan. Deklarasikan aktivitas ini dengan
tindakan intent dan skema URI konten yang wajib.
Untuk melindungi dari pemanggilan yang tidak sah dan serangan wakil yang bingung, deklarasikan
android:requireContentUriPermissionFromCaller="write" di Android 14 (level API
34) dan yang lebih tinggi. Hal ini menginstruksikan sistem operasi Android untuk memverifikasi bahwa
pemanggil yang memulai peluncuran memiliki izin tulis ke URI konten tujuan
sebelum mengizinkan aktivitas dimulai, seperti yang ditunjukkan dalam contoh berikut:
<activity
android:name="com.example.CredentialExportActivity"
android:exported="true"
android:requireContentUriPermissionFromCaller="write"
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 Anda, gunakan
IntentHandler.retrieveProviderImportCredentialsRequest(intent) untuk mengurai
permintaan, memverifikasi komponen panggilan dan metadata permintaan, melakukan
autentikasi biometrik yang diperlukan, dan menulis payload FIDO CXF kembali ke
URI konten yang diberikan.
Mengautentikasi permintaan
Karena aktivitas Anda diekspor, lindungi alur ekspor kredensial dengan memverifikasi setiap lapisan berikut:
- Verifikasi nonce sesi
credId(pertahanan utama): Saat mendaftarkanExportEntry, buatid(nonce) dengan entropi tinggi yang acak secara kriptografis dan kaitkan dengan sesi singkat. Di aktivitas Anda, pastikanrequest.credIdcocok dengan nonce sesi aktif dan belum berakhir yang sebelumnya dikeluarkan oleh penyedia Anda. Karena aplikasi pihak ketiga tidak dapat menebak nonce ini, hal ini memastikan bahwa intent sesuai dengan pilihan pengguna yang autentik pada pemilih sistem. - Verifikasi
Activity.getCallingPackage()(pemeriksaan pemanggil): Karena pemilih kredensial meluncurkan aktivitas Anda menggunakanstartActivityForResult, aktivitas Anda dapat memeriksagetCallingPackage()untuk mengonfirmasi bahwa pemanggil langsung yang meluncurkan UI adalah framework pemilih kredensial sistem tepercaya (di perangkat Android dengan layanan Google Play,"com.google.android.gms"). Tolak permintaan jika paket pemanggil tidak dikenali. - Periksa
request.callingAppInfo(informasi aplikasi target):callingAppInfomengidentifikasi aplikasi tujuan yang akan menerima kredensial yang diimpor. Anda dapat menggunakan informasi ini sesuai kebutuhan untuk transparansi (seperti menampilkan nama dan ikon aplikasi tujuan di UI konfirmasi) atau memverifikasi apakah aplikasi tujuan tepercaya.
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() } }
Mengekspor pengecualian pendaftaran dan pembersihan
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 dalamExportEntry, atau panggilan pada tingkat OS yang tidak didukung).RegisterExportUnknownErrorExceptionatauClearExportUnknownErrorException: Terjadi error sistem atau penyimpanan yang tidak diklasifikasikan saat memperbarui registry ekspor.
Mengimplementasikan pengimpor
Untuk mengimpor kredensial ke aplikasi Anda (misalnya, selama orientasi atau impor penyedia), terapkan peran pengimpor dan mulai alur dengan memanggil
ProviderEventsManager.importCredentials().
Buat permintaan impor dan alur peluncuran
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 memunculkan pengecualian penolakan.ImportCredentialsProviderConfigurationException: Error konfigurasi (misalnya, setcredentialTypeskosong diImportCredentialsRequestatau izin tidak ada).ImportCredentialsInvalidJsonException: Pengekspor menampilkan payload JSON yang salah format atau kosong yang gagal divalidasi permintaan.ImportCredentialsSystemErrorException: Terjadi error transfer Binder atau sistem Android internal saat mengimpor.ImportCredentialsUnknownCallerException: Aplikasi pemanggil tidak dapat diverifikasi oleh framework.ImportCredentialsUnknownErrorException: Error yang tidak terklasifikasi 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 pos atau pengiriman untuk pengisian otomatis formulir. |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
Token dan kunci 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" |
Referensi metadata dan placeholder untuk file biner. |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
Sandi aman buatan mesin. |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
Kartu identitas nasional, SSN, TIN, atau referensi 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. |
Pencocokan WASM (WebAssembly) kustom (Lanjutan)
Secara default, memanggil RegisterExportRequest.create(context, entries) akan menggabungkan
credential_transfer_matcher.wasm default sistem dari aset library, yang
memfilter entri hanya berdasarkan persimpangan 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)