Le API di trasferimento delle credenziali di Credential Manager consentono il trasferimento sicuro delle credenziali utente tra i fornitori di credenziali sullo stesso dispositivo. Questa guida spiega in dettaglio
come i fornitori di credenziali su Android possono integrarsi con le API fornite dalla
androidx.credentials:providerevents libreria. Questa funzionalità supporta
password, passkey, informazioni sull'indirizzo e campi personalizzati utilizzando il
formato di scambio delle credenziali FIDO (CXF) standardizzato.
Concetti principali
Il framework di trasferimento delle credenziali facilita il trasferimento delle credenziali peer-to-peer sullo stesso dispositivo senza esporre le credenziali non elaborate al sistema operativo Android o alle app non autenticate.
Il framework definisce due ruoli principali:
- Esportatore (fornitore di origine): un fornitore di credenziali che attualmente detiene le credenziali utente. Pre-registra i metadati relativi agli account esportabili
disponibili (
ExportEntry) con il sistema e risponde alle richieste di trasferimento quando selezionate dall'utente. - Importatore (fornitore client o procedura guidata di configurazione): un fornitore di credenziali o
una procedura guidata di configurazione che avvia una richiesta di importazione
(
ImportCredentialsRequest) specificando i tipi di credenziali e di estensioni che può ricevere.
Compatibilità con la versione di Android
L'API di trasferimento delle credenziali funziona sui dispositivi con Android 8 (livello API 26) e versioni successive.
Aggiungi dipendenze
Aggiungi la dipendenza androidx.credentials:providerevents a
build.gradle o build.gradle.kts del modulo:
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
Crea un'istanza della classe richiesta
Crea un'istanza di ProviderEventsManager richiesta.
val providerEventsManager = ProviderEventsManager.create(context)
Implementa l'esportatore
Per consentire agli utenti di esportare le credenziali dalla tua app ad altri fornitori di credenziali sul dispositivo, implementa il ruolo di esportatore.
Registra le voci di esportazione
Quando il fornitore di credenziali cambia (ad esempio, un utente esegue l'accesso, aggiunge credenziali o modifica gli account), registra o aggiorna gli elementi ExportEntry utilizzando ProviderEventsManager.registerExport().
Ogni ExportEntry richiede:
id: un identificatore di stringa segreto generato in modo casuale che rappresenta in modo univoco questa voce di esportazione. Devi conservare questo ID in modo sicuro; ti servirà in un secondo momento per verificare le richieste di trasferimento in entrata.accountDisplayName: etichetta dell'account facoltativa (ad esempio,"Personal Account").userDisplayName: l'identificatore principale dell'utente (ad esempio,"alice@example.com").icon: un'iconaBitmapche rappresenta il fornitore o l'account (la libreria la ridimensiona automaticamente a PNG 32x32).supportedCredentialTypes: un insieme di costanti stringa diCredentialTypesche rappresentano i tipi contenuti in questa voce.
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) } }
Dichiara l'attività dell'esportatore nel file manifest
Quando l'utente seleziona ExportEntry nell'interfaccia utente del selettore di sistema, il sistema avvia l'Activity di gestione designata. Dichiara questa attività con l'azione intent obbligatoria e lo schema URI contenuto:
<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>
Gestisci l'intent di trasferimento nella tua attività
In CredentialExportActivity, utilizza IntentHandler.retrieveProviderImportCredentialsRequest(intent) per analizzare la richiesta, verificare l'app di chiamate e credId, eseguire l'autenticazione biometrica richiesta e scrivere il payload FIDO CXF nell'URI contenuto fornito:
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() } }
Eccezioni di registrazione ed eliminazione dell'esportazione
Tutte le eccezioni in questo modulo sono sottoclassi di
ImportCredentialsException, RegisterExportException o
ClearExportException.
RegisterExportProviderConfigurationExceptionoClearExportProviderConfigurationException: viene generata quando la registrazione o l'eliminazione non riesce a causa di problemi di configurazione del fornitore (ad esempio,supportedCredentialTypesvuoto in unExportEntryo chiamata su un livello di sistema operativo non supportato).RegisterExportUnknownErrorExceptionoClearExportUnknownErrorException: si è verificato un errore di sistema o di archiviazione non classificato durante l'aggiornamento del registro di esportazione.
Implementa l'importatore
Per importare le credenziali nella tua app (ad esempio, durante l'onboarding o l'importazione del fornitore
), implementa il ruolo di importatore e avvia il flusso chiamando
ProviderEventsManager.importCredentials().
Crea la richiesta di importazione e avvia il flusso
Specifica quali CredentialTypes e KnownExtensions supporta l'importatore:
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) {}
Eccezioni del flusso di importazione
Tutte le eccezioni in questo modulo sono sottoclassi di
ImportCredentialsException, RegisterExportException o
ClearExportException.
ImportCredentialsCancellationException: l'utente ha chiuso l'interfaccia utente del selettore o ha annullato l'attività di esportazione (Activity.RESULT_CANCELED).ImportCredentialsNoExportOptionException: nessuna voce di esportazione registrata corrisponde ai tipi di credenziali richiesti oppure l'esportatore selezionato ha generato un'eccezione di rifiuto.ImportCredentialsProviderConfigurationException: errore di configurazione (ad esempio, insiemecredentialTypesvuoto inImportCredentialsRequesto autorizzazioni mancanti).ImportCredentialsInvalidJsonException: l'esportatore ha restituito un payload JSON non valido o vuoto che non ha superato la convalida della richiesta.ImportCredentialsSystemErrorException: si è verificato un errore di trasferimento interno del sistema Android o di Binder durante l'importazione.ImportCredentialsUnknownCallerException: il framework non è riuscito a verificare l'app chiamante.ImportCredentialsUnknownErrorException: errore non classificato o imprevisto durante il flusso di importazione.
Tipi di credenziali ed estensioni supportati
L'oggetto androidx.credentials.providerevents.transfer.CredentialTypes definisce costanti stringa standardizzate corrispondenti ai tipi di elementi FIDO CXF:
| Costante | Valore (tipo cxf) |
Descrizione |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH |
"basic-auth" |
Credenziali di accesso con nome utente e password. |
CREDENTIAL_TYPE_PUBLIC_KEY |
"passkey" |
Credenziali della chiave pubblica della passkey FIDO2 o WebAuthn. |
CREDENTIAL_TYPE_ADDRESS |
"address" |
Informazioni sull'indirizzo postale o di spedizione per il completamento automatico dei moduli. |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
Chiavi e token di accesso alle API. |
CREDENTIAL_TYPE_CREDIT_CARD |
"credit-card" |
Dati di pagamento con carta di credito e di debito. |
CREDENTIAL_TYPE_CUSTOM_FIELDS |
"custom-fields" |
Raggruppamenti personalizzati o campi definiti dall'utente. |
CREDENTIAL_TYPE_DRIVERS_LICENSE |
"drivers-license" |
Dettagli della patente di guida. |
CREDENTIAL_TYPE_FILE |
"file" |
Metadati e riferimenti ai segnaposto per i file binari. |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
Password sicure generate automaticamente. |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
Riferimenti a carte di identità nazionali, SSN, TIN o passaporti. |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
Link logici che rimandano a un altro elemento nel payload. |
CREDENTIAL_TYPE_NOTE |
"note" |
Note sicure definite dall'utente (stringa UTF-8). |
CREDENTIAL_TYPE_PASSPORT |
"passport" |
Dettagli del documento di viaggio del passaporto. |
CREDENTIAL_TYPE_PERSON_NAME |
"person-name" |
Dettagli sull'identità e sul nome della persona. |
CREDENTIAL_TYPE_SSH_KEY |
"ssh-key" |
Coppie di chiavi pubblica e privata SSH. |
CREDENTIAL_TYPE_TOTP |
"totp" |
Segreti della password monouso basata sul tempo (2FA). |
CREDENTIAL_TYPE_WIFI |
"wifi" |
SSID e passphrase della rete Wi-Fi. |
Matcher WASM (WebAssembly) personalizzati (avanzati)
Per impostazione predefinita, se chiami RegisterExportRequest.create(context, entries), viene incluso credential_transfer_matcher.wasm predefinito del sistema dagli asset della libreria, che filtra le voci in base alla sola intersezione di supportedCredentialTypes.
Se il fornitore di credenziali richiede una logica di corrispondenza complessa (ad esempio, controlli dinamici delle funzionalità o filtri condizionali basati su campi personalizzati), puoi compilare il tuo modulo WebAssembly corrispondente all'API di trasferimento delle credenziali WASM e passare direttamente l'array di byte non elaborati:
// 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)