As APIs Credentials Transfer do Gerenciador de credenciais permitem a transferência segura, no mesmo dispositivo,
de credenciais de usuário entre provedores de credenciais. Este guia detalha
como os provedores de credenciais no Android podem se integrar às APIs fornecidas pela
biblioteca androidx.credentials:providerevents. Esse recurso é compatível com
senhas, chaves de acesso, informações de endereço e campos personalizados usando o
Formato de troca de credenciais (CXF, na sigla em inglês) da FIDO padronizado.
Principais conceitos
O framework de transferência de credenciais facilita a transferência de credenciais ponto a ponto no mesmo dispositivo sem expor credenciais brutas ao SO Android ou a apps não autenticados.
O framework define dois papéis principais:
- Exportador (provedor de origem): um provedor de credenciais que atualmente contém
credenciais do usuário. Ele faz o pré-registro de metadados sobre contas exportáveis
disponíveis (
ExportEntry) no sistema e responde a solicitações de transferência quando selecionado pelo usuário. - Importador (provedor de cliente ou assistente de configuração): um provedor de credenciais ou
assistente de configuração que inicia uma solicitação de importação
(
ImportCredentialsRequest) especificando quais tipos de credenciais e extensões ele pode receber.
Compatibilidade com versões do Android
A API Credentials Transfer funciona em dispositivos com o Android 8 (nível 26 da API) e versões mais recentes.
Adicionar dependências
Adicione a dependência androidx.credentials:providerevents ao build.gradle ou build.gradle.kts do módulo:
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
Instanciar a classe necessária
Crie uma instância do ProviderEventsManager necessário.
val providerEventsManager = ProviderEventsManager.create(context)
Implementar o exportador
Para permitir que os usuários exportem credenciais do seu app para outros provedores de credenciais no dispositivo, implemente o papel de exportador.
Registrar entradas de exportação
Quando o provedor de credenciais mudar (por exemplo, um usuário fizer login, adicionar
credenciais ou modificar contas), registre ou atualize os itens ExportEntry
usando ProviderEventsManager.registerExport().
Cada ExportEntry requer:
id: um identificador de string secreto gerado aleatoriamente que representa de forma exclusiva essa entrada de exportação. É preciso persistir esse ID em segurança. Você vai precisar dele mais tarde para verificar as solicitações de transferência recebidas.accountDisplayName: rótulo opcional da conta (por exemplo,"Personal Account").userDisplayName: o identificador principal do usuário (por exemplo,"alice@example.com").icon: um íconeBitmapque representa o provedor ou a conta (dimensionado automaticamente para PNG de 32 x 32 pela biblioteca).supportedCredentialTypes: um conjunto de constantes de string deCredentialTypesque representam os tipos que essa entrada contém.
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) } }
Declarar a atividade do exportador no arquivo de manifesto
Quando o usuário seleciona seu ExportEntry na interface do seletor do sistema, o sistema
inicia o Activity de processamento designado. Declare essa atividade com a
ação de intent obrigatória e o esquema de URI de conteúdo.
Para se proteger contra invocações não autorizadas e ataques de deputado confuso, declare
android:requireContentUriPermissionFromCaller="write" no Android 14 (nível 34 da API)
e versões mais recentes. Isso instrui o sistema operacional Android a verificar se o
autor da chamada que inicia o lançamento tem permissões de gravação no URI de conteúdo
de destino antes de permitir que a atividade seja iniciada, conforme mostrado no exemplo a seguir:
<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>
Processar a intent de transferência na sua atividade
No CredentialExportActivity, use
IntentHandler.retrieveProviderImportCredentialsRequest(intent) para analisar a
solicitação, verificar o componente de chamada e os metadados da solicitação, realizar qualquer
autenticação biométrica necessária e gravar o payload do FIDO CXF de volta no
URI de conteúdo fornecido.
Autenticar a solicitação
Como sua atividade é exportada, proteja o fluxo de exportação de credenciais verificando cada uma das seguintes camadas:
- Verifique o valor de uso único da sessão
credId(defesa principal): ao registrar seuExportEntry, gere umid(valor de uso único) criptograficamente aleatório e de alta entropia e associe-o a uma sessão de curta duração. Na sua atividade, verifique serequest.credIdcorresponde a um valor de uso único de sessão ativo e não expirado emitido anteriormente pelo seu provedor. Como os apps de terceiros não podem adivinhar esse valor de uso único, isso garante que a intent corresponda a uma seleção de usuário autêntica no seletor do sistema. - Verificar
Activity.getCallingPackage()(verificação do autor da chamada): como o seletor de credenciais inicia sua atividade usandostartActivityForResult, ela pode inspecionargetCallingPackage()para confirmar se o autor da chamada imediato que inicia a IU é o framework confiável do seletor de credenciais do sistema (em dispositivos Android com o Google Play Services,"com.google.android.gms"). Rejeite a solicitação se o pacote de chamada não for reconhecido. - Inspecione
request.callingAppInfo(informações do app de destino): ocallingAppInfoidentifica o aplicativo de destino que vai receber as credenciais importadas. Você pode usar essas informações como achar melhor para fins de transparência (como exibir o nome e o ícone do app de destino na interface de confirmação) ou para verificar se o aplicativo de destino é confiável.
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() } }
Exportar registro e limpar exceções
Todas as exceções neste módulo são subclasses de
ImportCredentialsException, RegisterExportException ou
ClearExportException.
RegisterExportProviderConfigurationExceptionouClearExportProviderConfigurationException: gerado quando o registro ou a limpeza falha devido a problemas de configuração do provedor (por exemplo,supportedCredentialTypesvazio em umExportEntryou chamada em um nível de SO não compatível).RegisterExportUnknownErrorExceptionouClearExportUnknownErrorException: ocorreu um erro não classificado de sistema ou armazenamento ao atualizar o registro de exportação.
Implementar o importador
Para importar credenciais para o app (por exemplo, durante a integração ou a importação do provedor), implemente a função de importador e inicie o fluxo chamando
ProviderEventsManager.importCredentials().
Criar a solicitação de importação e iniciar o fluxo
Especifique quais CredentialTypes e KnownExtensions são compatíveis com o importador:
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) {}
Importar exceções de fluxo
Todas as exceções neste módulo são subclasses de
ImportCredentialsException, RegisterExportException ou
ClearExportException.
ImportCredentialsCancellationException: o usuário dispensou a interface do seletor ou cancelou a atividade de exportação (Activity.RESULT_CANCELED).ImportCredentialsNoExportOptionException: nenhuma entrada de exportação registrada correspondeu aos tipos de credencial solicitados ou o exportador selecionado gerou uma exceção de rejeição.ImportCredentialsProviderConfigurationException: erro de configuração (por exemplo,credentialTypesvazio definido emImportCredentialsRequestou permissões ausentes).ImportCredentialsInvalidJsonException: o exportador retornou um payload JSON vazio ou com formato incorreto que falhou na validação da solicitação.ImportCredentialsSystemErrorException: ocorreu um erro interno do sistema Android ou de transferência do Binder durante a importação.ImportCredentialsUnknownCallerException: o app de ligação não pôde ser verificado pelo framework.ImportCredentialsUnknownErrorException: erro não classificado ou inesperado durante o fluxo de importação.
Tipos de credenciais e extensões compatíveis
O objeto androidx.credentials.providerevents.transfer.CredentialTypes
define constantes de string padronizadas que correspondem aos tipos de item do FIDO CXF:
| Constante | Valor (tipo cxf) |
Descrição |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH |
"basic-auth" |
Credenciais de login com nome de usuário e senha. |
CREDENTIAL_TYPE_PUBLIC_KEY |
"passkey" |
Credenciais de chave pública de chave de acesso FIDO2 ou WebAuthn. |
CREDENTIAL_TYPE_ADDRESS |
"address" |
Informações de endereço postal ou de entrega para preenchimento automático de formulários. |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
Chaves e tokens de acesso à API. |
CREDENTIAL_TYPE_CREDIT_CARD |
"credit-card" |
Informações de pagamento com cartão de crédito e débito. |
CREDENTIAL_TYPE_CUSTOM_FIELDS |
"custom-fields" |
Agrupamentos personalizados ou campos definidos pelo usuário. |
CREDENTIAL_TYPE_DRIVERS_LICENSE |
"drivers-license" |
Detalhes da carteira de habilitação. |
CREDENTIAL_TYPE_FILE |
"file" |
Metadados e referências de marcadores de posição para arquivos binários. |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
Senhas seguras geradas por máquina. |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
Documentos de identidade nacionais, SSN, TIN ou referências de passaporte. |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
Links lógicos que apontam para outro item no payload. |
CREDENTIAL_TYPE_NOTE |
"note" |
Notas seguras definidas pelo usuário (string UTF-8). |
CREDENTIAL_TYPE_PASSPORT |
"passport" |
Detalhes do documento de viagem do passaporte. |
CREDENTIAL_TYPE_PERSON_NAME |
"person-name" |
Detalhes de identidade e nome da pessoa. |
CREDENTIAL_TYPE_SSH_KEY |
"ssh-key" |
Pares de chaves públicas e privadas SSH. |
CREDENTIAL_TYPE_TOTP |
"totp" |
Secrets de senha única baseada em tempo (2FA). |
CREDENTIAL_TYPE_WIFI |
"wifi" |
SSID e senhas longas da rede Wi-Fi. |
Correspondências personalizadas do WASM (WebAssembly) (avançado)
Por padrão, chamar RegisterExportRequest.create(context, entries) agrupa o
credential_transfer_matcher.wasm padrão do sistema dos recursos da biblioteca, que
filtra as entradas com base apenas na interseção de supportedCredentialTypes.
Se o provedor de credenciais exigir uma lógica de correspondência complexa (por exemplo, verificações de capacidade dinâmica ou filtragem condicional com base em campos personalizados), você poderá compilar seu próprio módulo WebAssembly correspondente à API WASM de transferência de credenciais e transmitir a matriz de bytes brutos diretamente:
// 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)