O Gerenciador de credenciais se refere a um conjunto de APIs introduzidas no Android 14, que oferecem suporte a vários métodos de login, como nome de usuário, senha e chaves de acesso, e soluções de login federadas (como o recurso Fazer login com o Google). Quando a API Credential Manager é invocada, o sistema Android agrega as credenciais de todos os provedores de credenciais instalados no dispositivo. Neste documento, descrevemos o conjunto de APIs que fornecem endpoints de integração para esses provedores de credenciais.
Configurar
Antes de implementar a funcionalidade no seu provedor de credenciais, conclua as etapas de configuração mostradas nas seções abaixo.
Declarar dependências
No arquivo build.gradle
do módulo, declare uma dependência usando a versão
mais recente da biblioteca do Gerenciador de credenciais:
implementation "androidx.credentials:credentials:1.2.0-{latest}"
Declarar o elemento de serviço no arquivo de manifesto
No arquivo de manifesto do app AndroidManifest.xml
, inclua uma declaração <service>
para uma classe de serviço que estende a classe
CredentialProviderService
da biblioteca androidx.credentials,
conforme mostrado no exemplo abaixo.
<service android:name=".MyCredentialProviderService"
android:enabled="true"
android:exported="true"
android:label="My Credential Provider"
android:icon="<any drawable icon>"
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE">
<intent-filter>
<action android:name="android.service.credentials.CredentialProviderService"/>
</intent-filter>
<meta-data
android:name="android.credentials.provider"
android:resource="@xml/provider"/>
</service>
A permissão e o filtro de intent mostrados acima são essenciais para que o fluxo do Gerenciador de credenciais funcione conforme o esperado. A permissão é necessária para que apenas o sistema Android possa se vincular a esse serviço. O filtro de intent é usado para facilitar a descoberta desse serviço como um provedor de credenciais a ser usado pelo Gerenciador de credenciais.
Declarar tipos de credenciais com suporte
No diretório res/xml
, crie um arquivo com o nome provider.xml
. Nesse
arquivo, declare os tipos de credenciais que o serviço aceita usando constantes
definidas para cada tipo de credencial na biblioteca. No exemplo abaixo,
o serviço oferece suporte para senhas tradicionais e chaves de acesso,
com constantes definidas como
TYPE_PASSWORD_CREDENTIAL
e TYPE_PUBLIC_KEY_CREDENTIAL
:
<?xml version="1.0" encoding="utf-8"?>
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
<capabilities>
<capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>
Nos níveis anteriores da API, os provedores de credenciais se integravam a APIs como o preenchimento automático para senhas e outros dados. Esses provedores podem usar a mesma infraestrutura interna para armazenar os tipos de credenciais atuais e também expandi-la para oferecer suporte a outros tipos, incluindo chaves de acesso.
Abordagem em duas fases para interação com o provedor
O Gerenciador de credenciais interage com os provedores de credenciais em duas fases:
- A primeira é a fase de início/consulta em que o sistema se vincula aos
serviços do provedor de credenciais e invoca métodos
onBeginGetCredentialRequest()
,onBeginCreateCredentialRequest()
ouonClearCredentialStateRequest()
com solicitaçõesBegin…
. Os provedores precisam processar essas solicitações e responder comBegin…
, preenchendo-as com entradas que representam opções visuais a serem mostradas no seletor de conta. Cada entrada precisa ter um conjuntoPendingIntent
. - Depois que o usuário seleciona uma entrada, a fase de seleção começa, e a
PendingIntent
associada à entrada é acionada, mostrando a atividade do provedor correspondente. Quando o usuário terminar de interagir com essa atividade, o provedor de credenciais vai precisar definir a resposta para o resultado da atividade antes de encerrá-la. Essa resposta é enviada ao app cliente que invocou o Gerenciador de credenciais.
Processar a criação da chave de acesso
Processar consultas para a criação de chaves de acesso
Quando um app cliente quer criar uma chave de acesso e armazená-la com um
provedor de credenciais, ele chama a API createCredential
. Para processar essa
solicitação no serviço do provedor de credenciais para que a chave de acesso seja realmente
mantida no seu armazenamento, conclua as etapas mostradas nas seções abaixo.
- Modifique o método
onBeginCreateCredentialRequest()
no serviço estendido deCredentialProviderService
. - Gerencie a
BeginCreateCredentialRequest
construindo umaBeginCreateCredentialResponse
correspondente e transmitindo-a com o callback. - Ao criar a
BeginCreateCredentialResponse
, adicioneCreateEntries
necessário. CadaCreateEntry
precisa corresponder a uma conta em que a credencial possa ser salva e precisa ter umaPendingIntent
definida com outros metadados.
O exemplo abaixo ilustra como realizar essas etapas:
override fun onBeginCreateCredentialRequest(
request: BeginCreateCredentialRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException>,
) {
val response: BeginCreateCredentialResponse? = processCreateCredentialRequest(request)
if (response != null) {
callback.onResult(response)
} else {
callback.onError(CreateCredentialUnknownException())
}
}
fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse? {
when (request) {
is BeginCreatePublicKeyCredentialRequest -> {
// Request is passkey type
return handleCreatePasskeyQuery(request)
}
}
// Request not supported
return null
}
private fun handleCreatePasskeyQuery(
request: BeginCreatePublicKeyCredentialRequest
): BeginCreateCredentialResponse {
// Adding two create entries - one for storing credentials to the 'Personal'
// account, and one for storing them to the 'Family' account. These
// accounts are local to this sample app only.
val createEntries: MutableList<CreateEntry> = mutableListOf()
createEntries.add( CreateEntry(
PERSONAL_ACCOUNT_ID,
createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
))
createEntries.add( CreateEntry(
FAMILY_ACCOUNT_ID,
createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
))
return BeginCreateCredentialResponse(createEntries)
}
private fun createNewPendingIntent(accountId: String, action: String): PendingIntent {
val intent = Intent(action).setPackage(PACKAGE_NAME)
// Add your local account ID as an extra to the intent, so that when
// user selects this entry, the credential can be saved to this
// account
intent.putExtra(EXTRA_KEY_ACCOUNT_ID, accountId)
return PendingIntent.getActivity(
applicationContext, UNIQUE_REQ_CODE,
intent, (
PendingIntent.FLAG_MUTABLE
or PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
Sua construção PendingIntent
precisa aderir ao seguinte:
- A atividade correspondente precisa ser configurada para mostrar qualquer solicitação, confirmação ou seleção biométrica necessária.
- Todos os dados necessários para o provedor quando a atividade correspondente é
invocada precisam ser definidos como um extra na intent usada para criar sua
PendingIntent
, como umaccountId
no fluxo de criação. - A
PendingIntent
precisa ser criada com a flagPendingIntent.FLAG_MUTABLE
para que o sistema possa anexar a solicitação final ao extra da intent. - Sua
PendingIntent
não pode ser construída com a flagPendingIntent.FLAG_ONE_SHOT
, porque o usuário pode selecionar uma entrada, voltar e selecioná-la de novo, o que faria com que aPendingIntent
fosse disparada duas vezes. - A
PendingIntent
precisa ser construída com um código de solicitação exclusivo para que cada entrada tenha a própriaPendingIntent
correspondente.
Processar a seleção de entrada para solicitações de criação de chaves de acesso
- Quando o usuário seleciona uma
CreateEntry
preenchida anteriormente, aPendingIntent
correspondente é invocada e o provedor associado àActivity
é criado. - Depois que o método
onCreate
da atividade for invocado, acesse a intent associada e a transmita para a classePendingIntentHander
para extrair aProviderCreateCredentialRequest
. - Extraia o
requestJson
,callingAppInfo
e oclientDataHash
da solicitação. - Extraia o
accountId
local do extra da intent. Este é um exemplo específico de implementação do app e não é necessário. Esse ID da conta pode ser usado para armazenar a credencial em relação a ele. - Valide o
requestJson
. O exemplo abaixo usa classes de dados locais, comoPublicKeyCredentialCreationOptions
, para converter o JSON de entrada em uma classe estruturada, de acordo com a especificação do WebAuthn. Como provedor de credenciais, você pode substituí-lo por seu próprio analisador. - Verifique o link de recursos do app de chamada se a chamada for originada de um app Android nativo.
- Mostrar um prompt de autenticação. O exemplo abaixo usa a API Biometric (link em inglês) do Android.
- Quando a autenticação for bem-sucedida, gere um
credentialId
e um par de chaves (link em inglês). - Salve a chave privada (link em inglês) no seu banco de dados local em
callingAppInfo.packageName
. - Crie uma resposta JSON da API Web Authentication que
consiste na chave pública e no
credentialId
(links em inglês). O exemplo abaixo usa classes de utilitários locais, comoAuthenticatorAttestationResponse
eFidoPublicKeyCredential
que ajudam a criar um JSON com base na especificação mencionada anteriormente. Como provedor de credenciais, você pode substituir essas classes pelos seus próprios builders. - Crie uma
CreatePublicKeyCredentialResponse
com o JSON gerado acima. - Defina
CreatePublicKeyCredentialResponse
como um extra em umaIntent
usandoPendingIntentHander.setCreateCredentialResponse()
e defina essa intent como o resultado da atividade. - Conclua a atividade.
O exemplo de código abaixo ilustra essas etapas. Esse código precisa ser processado na
classe Activity assim que onCreate()
for invocado.
val request =
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
if (request != null && request.callingRequest is CreatePublicKeyCredentialRequest) {
val publicKeyRequest: CreatePublicKeyCredentialRequest =
request.callingRequest as CreatePublicKeyCredentialRequest
createPasskey(
publicKeyRequest.requestJson,
request.callingAppInfo,
publicKeyRequest.clientDataHash,
accountId
)
}
fun createPasskey(
requestJson: String,
callingAppInfo: CallingAppInfo?,
clientDataHash: ByteArray?,
accountId: String?
) {
val request = PublicKeyCredentialCreationOptions(requestJson)
val biometricPrompt = BiometricPrompt(
this,
<executor>,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int, errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
// Generate a credentialId
val credentialId = ByteArray(32)
SecureRandom().nextBytes(credentialId)
// Generate a credential key pair
val spec = ECGenParameterSpec("secp256r1")
val keyPairGen = KeyPairGenerator.getInstance("EC");
keyPairGen.initialize(spec)
val keyPair = keyPairGen.genKeyPair()
// Save passkey in your database as per your own implementation
// Create AuthenticatorAttestationResponse object to pass to
// FidoPublicKeyCredential
val response = AuthenticatorAttestationResponse(
requestOptions = request,
credentialId = credentialId,
credentialPublicKey = getPublicKeyFromKeyPair(keyPair),
origin = appInfoToOrigin(callingAppInfo),
up = true,
uv = true,
be = true,
bs = true,
packageName = callingAppInfo.packageName
)
val credential = FidoPublicKeyCredential(
rawId = credentialId, response = response
)
val result = Intent()
val createPublicKeyCredResponse =
CreatePublicKeyCredentialResponse(credential.json())
// Set the CreateCredentialResponse as the result of the Activity
PendingIntentHandler.setCreateCredentialResponse(
result, createPublicKeyCredResponse
)
setResult(Activity.RESULT_OK, result)
finish()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Use your screen lock")
.setSubtitle("Create passkey for ${request.rp.name}")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
/* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
)
.build()
biometricPrompt.authenticate(promptInfo)
}
fun appInfoToOrigin(info: CallingAppInfo): String {
val cert = info.signingInfo.apkContentsSigners[0].toByteArray()
val md = MessageDigest.getInstance("SHA-256");
val certHash = md.digest(cert)
// This is the format for origin
return "android:apk-key-hash:${b64Encode(certHash)}"
}
Processar consultas de solicitações de criação de senha
Para processar consultas de solicitações de criação de senha, faça o seguinte:
- No método
processCreateCredentialRequest()
mencionado na seção anterior, adicione outro caso dentro do bloco de alternância para processar solicitações de senha. - Ao criar a
BeginCreateCredentialResponse
, adicione asCreateEntries
necessárias. - Cada
CreateEntry
precisa corresponder a uma conta em que a credencial possa ser salva e ter umaPendingIntent
definida, além de outros metadados.
O exemplo abaixo ilustra como implementar essas etapas:
fun processCreateCredentialRequest(
request: BeginCreateCredentialRequest
): BeginCreateCredentialResponse? {
when (request) {
is BeginCreatePublicKeyCredentialRequest -> {
// Request is passkey type
return handleCreatePasskeyQuery(request)
}
is BeginCreatePasswordCredentialRequest -> {
// Request is password type
return handleCreatePasswordQuery(request)
}
}
return null
}
private fun handleCreatePasswordQuery(
request: BeginCreatePasswordCredentialRequest
): BeginCreateCredentialResponse {
val createEntries: MutableList<CreateEntry> = mutableListOf()
// Adding two create entries - one for storing credentials to the 'Personal'
// account, and one for storing them to the 'Family' account. These
// accounts are local to this sample app only.
createEntries.add(
CreateEntry(
PERSONAL_ACCOUNT_ID,
createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
)
)
createEntries.add(
CreateEntry(
FAMILY_ACCOUNT_ID,
createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
)
)
return BeginCreateCredentialResponse(createEntries)
}
Processar a seleção de entrada para solicitações de criação de senha
Quando o usuário seleciona uma CreateEntry
preenchida, a
PendingIntent
correspondente é executada e mostra a atividade associada. Acesse a
intent associada transmitida em onCreate
e a transmita para a classe
PendingIntentHander
para extrair o método ProviderCreateCredentialRequest
.
O exemplo abaixo ilustra como implementar esse processo. Esse código precisa
ser processado no método onCreate()
da atividade.
val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest
// Fetch the ID and password from the request and save it in your database
<your_database>.addNewPassword(
PasswordInfo(
request.id,
request.password,
createRequest.callingAppInfo.packageName
)
)
//Set the final response back
val result = Intent()
val response = CreatePasswordResponse()
PendingIntentHandler.setCreateCredentialResponse(result, response)
setResult(Activity.RESULT_OK, result)
this@<activity>.finish()
Processar o login do usuário
O login do usuário é processado nestas etapas:
- Quando um app cliente tenta fazer o login de um usuário, ele prepara uma
instância
GetCredentialRequest
. - O framework do Android propaga essa solicitação para todos os provedores de credenciais aplicáveis pela vinculação a esses serviços.
- O serviço do provedor recebe uma
BeginGetCredentialRequest
que contém uma lista deBeginGetCredentialOption
, cada uma contendo parâmetros que podem ser usados para extrair as credenciais correspondentes.
Para processar essa solicitação no serviço do provedor de credenciais, siga estas etapas:
Substitua o método
onBeginGetCredentialRequest()
para processar a solicitação. Caso suas credenciais estejam bloqueadas, você vai poder definir imediatamente umaAuthenticationAction
na resposta e invocar o callback.private val unlockEntryTitle = "Authenticate to continue" override fun onBeginGetCredentialRequest( request: BeginGetCredentialRequest, cancellationSignal: CancellationSignal, callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>, ) { if (isAppLocked()) { callback.onResult(BeginGetCredentialResponse( authenticationActions = mutableListOf(AuthenticationAction( unlockEntryTitle, createUnlockPendingIntent()) ) ) ) return } try { response = processGetCredentialRequest(request) callback.onResult(response) } catch (e: GetCredentialException) { callback.onError(GetCredentialUnknownException()) } }
Os provedores que exigem o desbloqueio das credenciais antes de retornar qualquer
credentialEntries
precisam configurar uma intent pendente que leve o usuário ao fluxo de desbloqueio do app:private fun createUnlockPendingIntent(): PendingIntent { val intent = Intent(UNLOCK_INTENT).setPackage(PACKAGE_NAME) return PendingIntent.getActivity( applicationContext, UNIQUE_REQUEST_CODE, intent, ( PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) ) }
Extraia credenciais do seu banco de dados local e configure usando as
CredentialEntries
que aparecerem no seletor. Para chaves de acesso, é possível definir ocredentialId
como um extra na intent para saber a qual credencial ela é mapeada quando o usuário seleciona essa entrada.companion object { // These intent actions are specified for corresponding activities // that are to be invoked through the PendingIntent(s) private const val GET_PASSKEY_INTENT_ACTION = "PACKAGE_NAME.GET_PASSKEY" private const val GET_PASSWORD_INTENT_ACTION = "PACKAGE_NAME.GET_PASSWORD" } fun processGetCredentialsRequest( request: BeginGetCredentialRequest ): BeginGetCredentialResponse { val callingPackage = request.callingAppInfo?.packageName val credentialEntries: MutableList<CredentialEntry> = mutableListOf() for (option in request.beginGetCredentialOptions) { when (option) { is BeginGetPasswordOption -> { credentialEntries.addAll( populatePasswordData( callingPackage, option ) ) } is BeginGetPublicKeyCredentialOption -> { credentialEntries.addAll( populatePasskeyData( callingPackage, option ) ) ) } else -> { Log.i(TAG, "Request not supported") } } } return BeginGetCredentialResponse(credentialEntries) }
Consulte credenciais do banco de dados e crie entradas de chave de acesso e de senha para preencher.
private fun populatePasskeyData( callingAppInfo: CallingAppInfo, option: BeginGetPublicKeyCredentialOption ): List<CredentialEntry> { val passkeyEntries: MutableList<CredentialEntry> = mutableListOf() val request = PublicKeyCredentialRequestOptions(option.requestJson) // Get your credentials from database where you saved during creation flow val creds = <getCredentialsFromInternalDb(request.rpId)> val passkeys = creds.passkeys for (passkey in passkeys) { val data = Bundle() data.putString("credId", passkey.credId) passkeyEntries.add( PublicKeyCredentialEntry( context = applicationContext, username = passkey.username, pendingIntent = createNewPendingIntent( GET_PASSKEY_INTENT_ACTION, data ), beginPublicKeyCredentialOption = option, displayName = passkey.displayName, icon = passkey.icon ) ) } return passkeyEntries } // Fetch password credentials and create password entries to populate to // the user private fun populatePasswordData( callingPackage: String, option: BeginGetPasswordOption ): List<CredentialEntry> { val passwordEntries: MutableList<CredentialEntry> = mutableListOf() // Get your password credentials from database where you saved during // creation flow val creds = <getCredentialsFromInternalDb(callingPackage)> val passwords = creds.passwords for (password in passwords) { passwordEntries.add( PasswordCredentialEntry( context = applicationContext, username = password.username, pendingIntent = createNewPendingIntent( GET_PASSWORD_INTENT ), beginGetPasswordOption = option displayName = password.username, icon = password.icon ) ) } return passwordEntries } private fun createNewPendingIntent( action: String, extra: Bundle? = null ): PendingIntent { val intent = Intent(action).setPackage(PACKAGE_NAME) if (extra != null) { intent.putExtra("CREDENTIAL_DATA", extra) } return PendingIntent.getActivity( applicationContext, UNIQUE_REQUEST_CODE, intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) ) }
Depois de consultar e preencher as credenciais, você precisa gerenciar a fase de seleção das credenciais que estão sendo selecionadas pelo usuário, seja uma chave de acesso ou uma senha.
Como gerenciar a seleção de usuários para chaves de acesso
- No método
onCreate
da atividade correspondente, extraia a intent associada e transmita paraPendingIntentHandler.retrieveProviderGetCredentialRequest()
. - Extraia a
GetPublicKeyCredentialOption
da solicitação extraída acima. Em seguida, extraia orequestJson
e oclientDataHash
dessa opção. - Extraia o
credentialId
do extra da intent, que foi preenchido pelo provedor de credenciais quando aPendingIntent
correspondente foi configurada. - Extraia a chave de acesso do banco de dados local usando os parâmetros de solicitação acessados acima.
Declare que a chave de acesso é válida com metadados extraídos e com a verificação do usuário.
val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val publicKeyRequest = getRequest.credentialOption as GetPublicKeyCredentialOption val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA") val credIdEnc = requestInfo.getString("credId") // Get the saved passkey from your database based on the credential ID // from the publickeyRequest val passkey = <your database>.getPasskey(credIdEnc) // Decode the credential ID, private key and user ID val credId = b64Decode(credIdEnc) val privateKey = b64Decode(passkey.credPrivateKey) val uid = b64Decode(passkey.uid) val origin = appInfoToOrigin(getRequest.callingAppInfo) val packageName = getRequest.callingAppInfo.packageName validatePasskey( publicKeyRequest.requestJson, origin, packageName, uid, passkey.username, credId, privateKey )
Para validar o usuário, mostre um comando de biometria (ou outro método de declaração). O snippet de código abaixo usa a API Android Biometric.
Quando a autenticação for bem-sucedida, construa uma resposta JSON com base na especificação de declaração da autenticação da Web do W3 (em inglês). No snippet de código abaixo, classes de dados auxiliares como
AuthenticatorAssertionResponse
são usadas para receber parâmetros estruturados e convertê-los para o formato JSON necessário. A resposta contém uma assinatura digital (link em inglês) da chave privada de uma credencial do WebAuthn. O servidor da parte confiável pode verificar essa assinatura para autenticar um usuário antes de fazer login.Crie uma
PublicKeyCredential
usando o JSON gerado acima e defina-a em umaGetCredentialResponse
final. Defina essa resposta final no resultado dessa atividade.
O exemplo abaixo ilustra como essas etapas podem ser implementadas:
val request = PublicKeyCredentialRequestOptions(requestJson)
val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)
val biometricPrompt = BiometricPrompt(
this,
<executor>,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int, errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
val response = AuthenticatorAssertionResponse(
requestOptions = request,
credentialId = credId,
origin = origin,
up = true,
uv = true,
be = true,
bs = true,
userHandle = uid,
packageName = packageName
)
val sig = Signature.getInstance("SHA256withECDSA");
sig.initSign(privateKey)
sig.update(response.dataToSign())
response.signature = sig.sign()
val credential = FidoPublicKeyCredential(
rawId = credId, response = response
)
val result = Intent()
val passkeyCredential = PublicKeyCredential(credential.json)
PendingIntentHandler.setGetCredentialResponse(
result, GetCredentialResponse(passkeyCredential)
)
setResult(RESULT_OK, result)
finish()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Use your screen lock")
.setSubtitle("Use passkey for ${request.rpId}")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
/* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
)
.build()
biometricPrompt.authenticate(promptInfo)
Como gerenciar a seleção de usuários para autenticação de senha
- Na atividade correspondente, acesse a intent transmitida para
onCreate
e extraia aProviderGetCredentialRequest
usando oPendingIntentHandler
. Use
GetPasswordOption
na solicitação para extrair as credenciais de senha do nome do pacote recebido.val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val passwordOption = getRequest.credentialOption as GetPasswordCredentialOption val username = passwordOption.username // Fetch the credentials for the calling app package name val creds = <your_database>.getCredentials(callingAppInfo.packageName) val passwords = creds.passwords val it = passwords.iterator() var password = "" while (it.hasNext() == true) { val passwordItemCurrent = it.next() if (passwordItemCurrent.username == username) { password = passwordItemCurrent.password break } }
Depois de extraída, defina a resposta para a credencial de senha selecionada.
// Set the response back val result = Intent() val passwordCredential = PasswordCredential(username, password) PendingIntentHandler.setGetCredentialResponse( result, GetCredentialResponse(passwordCredential) ) setResult(Activity.RESULT_OK, result) finish()
Processar a seleção de uma entrada de ação de autenticação
Conforme mencionado anteriormente, um provedor de credenciais pode definir uma
AuthenticationAction
se as credenciais estiverem bloqueadas. Se o usuário selecionar essa
entrada, a atividade correspondente à ação da intent definida na
PendingIntent
será invocada. Os provedores de credenciais podem mostrar um fluxo de autenticação
biométrica ou um mecanismo semelhante para desbloquear as credenciais. Se for bem-sucedido,
o provedor de credenciais vai precisar criar uma BeginGetCredentialResponse
, semelhante
à maneira como o processamento de login do usuário é descrito acima, já que as credenciais estão
desbloqueadas. Essa resposta precisa ser definida pelo método
PendingIntentHandler.setBeginGetCredentialResponse()
antes
que a intent preparada seja definida como o resultado e a atividade seja concluída.
Limpar solicitações de credenciais
Um app cliente pode solicitar que qualquer estado armazenado para seleção de credenciais seja
limpado, por exemplo, para um provedor de credenciais que pode se lembrar da credencial
selecionada anteriormente e só retornar isso na próxima vez. Um app cliente chama essa API e
espera que essa seleção fixa seja apagada. O serviço do provedor de credenciais
pode processar essa solicitação substituindo o método
onClearCredentialStateRequest()
:
override fun onClearCredentialStateRequest(
request: android.service.credentials.ClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>,
) {
// Delete any maintained state as appropriate.
}
Adicionar capacidade de vincular à página de configurações do seu provedor
Para permitir que os usuários abram as configurações do provedor na tela Senhas,
chaves de acesso e preenchimento automático, os apps de provedor de credenciais precisam implementar o
atributo de manifesto settingsActivity
credential-provider
em
res/xml/provider.xml
. Esse atributo permite que você use uma intent para abrir a própria
tela de configurações do app se um usuário clicar no nome de um provedor na lista de serviços Senhas,
chaves de acesso e preenchimento automático. Defina o valor desse atributo como o
nome da atividade a ser iniciada na tela de configurações.
<credential-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsSubtitle="Example settings provider name"
android:settingsActivity="com.example.SettingsActivity">
<capabilities>
<capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>
Intents de configurações
Abrir configurações: a intent android.settings.CREDENTIAL_PROVIDER
abre uma tela de configurações em que o usuário pode selecionar os provedores de credenciais
preferencial e adicionais.
Serviço de credencial preferencial: a
intent ACTION_REQUEST_SET_AUTOFILL_SERVICE
redireciona o usuário para a
tela de seleção do provedor preferencial. O provedor selecionado nesta tela
passa a ser o de preenchimento automático e de credenciais preferido.
Criar uma lista de permissões de apps privilegiados
Apps privilegiados, como navegadores da Web, fazem chamadas do Gerenciador de credenciais em nome de
outras partes confiáveis ao definir o parâmetro de origin
no Gerenciador de
credenciais nos métodos GetCredentialRequest()
e
CreatePublicKeyCredentialRequest()
. Para processar essas solicitações,
o provedor de credenciais recupera a origin
usando a API
getOrigin()
.
Para recuperar a origin
, o app do provedor de credenciais precisa transmitir uma lista de
autores de chamadas privilegiados e confiáveis para a
API androidx.credentials.provider.CallingAppInfo's getOrigin()
. A lista de permissões
precisa ser um objeto JSON válido. A origin
será retornada se o packageName
e
as impressões digitais do certificado fornecidas por signingInfo
corresponderem às de um app
encontrado na privilegedAllowlist
transmitida para a API getOrigin()
. Depois que o
valor de origin
é recebido, o app do provedor precisa considerar isso como uma chamada
privilegiada e definir essa origin
nos dados do cliente
no AuthenticatorResponse
, em vez de calcular a
origin
usando a assinatura do app de chamada.
Se você extrair uma origin
, use o clientDataHash
fornecido diretamente
em CreatePublicKeyCredentialRequest()
ou
GetPublicKeyCredentialOption()
em vez de criar e fazer o hash
de clientDataJSON
durante a solicitação de assinatura. Para evitar problemas de análise do JSON, defina
um valor de marcador para clientDataJSON
na resposta de atestado
e declaração.
O Gerenciador de senhas do Google usa uma lista de permissões (link em inglês) disponível para
chamadas para getOrigin()
. Como provedor de credenciais, use essa lista ou
forneça a sua no formato JSON descrito pela API. Cabe ao
provedor selecionar qual lista será usada. Para ter acesso privilegiado com provedores de
credenciais terceirizados, consulte a documentação fornecida por eles.
Ativar provedores em um dispositivo
Os usuários precisam ativar o provedor nas Configurações do dispositivo > Senhas e contas > Seu provedor > Ativar ou desativar.
fun createSettingsPendingIntent(): PendingIntent