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.
Configuração
Antes de implementar a funcionalidade no seu provedor de credenciais, conclua as etapas de configuração mostradas nas seções abaixo.
Declarar dependências
Adicione as seguintes dependências ao script de build do módulo do app para usar a versão mais recente da biblioteca do Gerenciador de credenciais:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.6.0-beta03") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.6.0-beta03" }
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="@mipmap/ic_launcher"
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
tools:targetApi="upside_down_cake">
<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 no exemplo anterior 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:
<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
PendingIntentassociada à 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
BeginCreateCredentialRequestconstruindo umaBeginCreateCredentialResponsecorrespondente e transmitindo-a com o callback. - Ao criar a
BeginCreateCredentialResponse, adicioneCreateEntriesnecessário. CadaCreateEntryprecisa corresponder a uma conta em que a credencial possa ser salva e precisa ter umaPendingIntentdefinida 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 umaccountIdno fluxo de criação. - A
PendingIntentprecisa ser criada com a flagPendingIntent.FLAG_MUTABLEpara que o sistema possa anexar a solicitação final ao extra da intent. - Sua
PendingIntentnã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 aPendingIntentfosse disparada duas vezes. - A
PendingIntentprecisa ser construída com um código de solicitação exclusivo para que cada entrada tenha a própriaPendingIntentcorrespondente.
Processar a seleção de entrada para solicitações de criação de chaves de acesso
- Quando o usuário seleciona uma
CreateEntrypreenchida anteriormente, aPendingIntentcorrespondente é invocada e o provedor associado àActivityé criado. - Depois que o método
onCreateda atividade for invocado, acesse a intent associada e a transmita para a classePendingIntentHanderpara extrair aProviderCreateCredentialRequest. - Extraia o
requestJson,callingAppInfoe oclientDataHashda solicitação. - Extraia o
accountIdlocal 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
credentialIde um par de chaves. - 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, comoAuthenticatorAttestationResponseeFidoPublicKeyCredentialque 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
CreatePublicKeyCredentialResponsecom o JSON gerado acima. - Defina
CreatePublicKeyCredentialResponsecomo um extra em umaIntentusandoPendingIntentHander.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.
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
// ...
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
)
}
}
@SuppressLint("RestrictedApi")
fun createPasskey(
requestJson: String,
callingAppInfo: CallingAppInfo?,
clientDataHash: ByteArray?,
accountId: String?
) {
val request = PublicKeyCredentialCreationOptions(requestJson)
val biometricPrompt = BiometricPrompt(
this,
{ }, // Pass in your own executor
object : AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
@RequiresApi(VERSION_CODES.P)
override fun onAuthenticationSucceeded(
result: 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,
authenticatorAttachment = "", // Add your authenticator attachment
)
val result = Intent()
val createPublicKeyCredResponse =
CreatePublicKeyCredentialResponse(credential.json())
// Set the CreateCredentialResponse as the result of the Activity
PendingIntentHandler.setCreateCredentialResponse(
result,
createPublicKeyCredResponse
)
setResult(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)
}
@RequiresApi(VERSION_CODES.P)
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 asCreateEntriesnecessárias. - Cada
CreateEntryprecisa corresponder a uma conta em que a credencial possa ser salva e ter umaPendingIntentdefinida, 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
}
@RequiresApi(VERSION_CODES.M)
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)
if (createRequest == null) {
return
}
val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest
// Fetch the ID and password from the request and save it in your database
mDatabase.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)
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
BeginGetCredentialRequestque 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 umaAuthenticationActionna 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
credentialEntriesprecisam 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
CredentialEntriesque aparecerem no seletor. Para chaves de acesso, é possível definir ocredentialIdcomo 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 processGetCredentialRequest( request: BeginGetCredentialRequest ): BeginGetCredentialResponse { val callingPackageInfo = request.callingAppInfo val callingPackageName = callingPackageInfo?.packageName.orEmpty() val credentialEntries: MutableList<CredentialEntry> = mutableListOf() for (option in request.beginGetCredentialOptions) { when (option) { is BeginGetPasswordOption -> { credentialEntries.addAll( populatePasswordData( callingPackageName, option ) ) } is BeginGetPublicKeyCredentialOption -> { credentialEntries.addAll( populatePasskeyData( callingPackageInfo, 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 ), beginGetPublicKeyCredentialOption = 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
onCreateda atividade correspondente, extraia a intent associada e transmita paraPendingIntentHandler.retrieveProviderGetCredentialRequest(). - Extraia a
GetPublicKeyCredentialOptionda solicitação extraída acima. Em seguida, extraia orequestJsone oclientDataHashdessa opção. - Extraia o
credentialIddo extra da intent, que foi preenchido pelo provedor de credenciais quando aPendingIntentcorrespondente 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?.credentialOptions?.first() as GetPublicKeyCredentialOption val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA") val credIdEnc = requestInfo?.getString("credId").orEmpty() // Get the saved passkey from your database based on the credential ID from the PublicKeyRequest val passkey = mDatabase.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
AuthenticatorAssertionResponsesã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
PublicKeyCredentialusando o JSON gerado acima e defina-a em umaGetCredentialResponsefinal. 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,
{ }, // Pass in your own 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,
authenticatorAttachment = "", // Add your authenticator attachment
)
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
onCreatee extraia aProviderGetCredentialRequestusando oPendingIntentHandler. Use
GetPasswordOptionna solicitação para extrair as credenciais de senha do nome do pacote recebido.val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val passwordOption = getRequest?.credentialOptions?.first() as GetPasswordOption val username = passwordOption.allowedUserIds.first() // Fetch the credentials for the calling app package name val creds = mDatabase.getCredentials(callingAppInfo.packageName) val passwords = creds.passwords val it = passwords.iterator() var password = "" while (it.hasNext()) { 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: ProviderClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>
) {
// Delete any maintained state as appropriate.
}
Adição da capacidade de vincular à página de configurações do 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 provedores de credenciais precisam implementar o atributo de manifesto credential-provider settingsActivity em res/xml/provider.xml. Com esse atributo, você pode usar uma intent para abrir a tela de configurações do seu 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 preferidos e
adicionais.
Serviço de credenciais preferido: a intent
ACTION_REQUEST_SET_AUTOFILL_SERVICE redireciona o usuário para a
tela de seleção do provedor preferido. O provedor selecionado nessa tela
se torna o provedor preferido de credenciais e preenchimento automático.
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 (links em inglês), 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