Integrar o Gerenciador de credenciais à sua solução de provedor de credenciais

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:

  1. 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() ou onClearCredentialStateRequest() com solicitações Begin…. Os provedores precisam processar essas solicitações e responder com Begin…, preenchendo-as com entradas que representam opções visuais a serem mostradas no seletor de conta. Cada entrada precisa ter um conjunto PendingIntent.
  2. 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.

  1. Modifique o método onBeginCreateCredentialRequest() no serviço estendido de CredentialProviderService.
  2. Gerencie a BeginCreateCredentialRequest construindo uma BeginCreateCredentialResponse correspondente e transmitindo-a com o callback.
  3. Ao criar a BeginCreateCredentialResponse, adicione CreateEntries necessário. Cada CreateEntry precisa corresponder a uma conta em que a credencial possa ser salva e precisa ter uma PendingIntent 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 um accountId no fluxo de criação.
  • A PendingIntent precisa ser criada com a flag PendingIntent.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 flag PendingIntent.FLAG_ONE_SHOT, porque o usuário pode selecionar uma entrada, voltar e selecioná-la de novo, o que faria com que a PendingIntent 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ópria PendingIntent correspondente.

Processar a seleção de entrada para solicitações de criação de chaves de acesso

  1. Quando o usuário seleciona uma CreateEntry preenchida anteriormente, a PendingIntent correspondente é invocada e o provedor associado à Activity é criado.
  2. Depois que o método onCreate da atividade for invocado, acesse a intent associada e a transmita para a classe PendingIntentHander para extrair a ProviderCreateCredentialRequest.
  3. Extraia o requestJson, callingAppInfo e o clientDataHash da solicitação.
  4. 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.
  5. Valide o requestJson. O exemplo abaixo usa classes de dados locais, como PublicKeyCredentialCreationOptions, 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.
  6. Verifique o link de recursos do app de chamada se a chamada for originada de um app Android nativo.
  7. Mostrar um prompt de autenticação. O exemplo abaixo usa a API Biometric (link em inglês) do Android.
  8. Quando a autenticação for bem-sucedida, gere um credentialId e um par de chaves (link em inglês).
  9. Salve a chave privada (link em inglês) no seu banco de dados local em callingAppInfo.packageName.
  10. 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, como AuthenticatorAttestationResponse e FidoPublicKeyCredential 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.
  11. Crie uma CreatePublicKeyCredentialResponse com o JSON gerado acima.
  12. Defina CreatePublicKeyCredentialResponse como um extra em uma Intent usando PendingIntentHander.setCreateCredentialResponse() e defina essa intent como o resultado da atividade.
  13. 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 as CreateEntries necessárias.
  • Cada CreateEntry precisa corresponder a uma conta em que a credencial possa ser salva e ter uma PendingIntent 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 de BeginGetCredentialOption, 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:

  1. Substitua o método onBeginGetCredentialRequest() para processar a solicitação. Caso suas credenciais estejam bloqueadas, você vai poder definir imediatamente uma AuthenticationAction 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
            )
        )
    }
    
  2. Extraia credenciais do seu banco de dados local e configure usando as CredentialEntries que aparecerem no seletor. Para chaves de acesso, é possível definir o credentialId 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)
    }
    
  3. 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)
        )
    }
    
  4. 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

  1. No método onCreate da atividade correspondente, extraia a intent associada e transmita para PendingIntentHandler.retrieveProviderGetCredentialRequest().
  2. Extraia a GetPublicKeyCredentialOption da solicitação extraída acima. Em seguida, extraia o requestJson e o clientDataHash dessa opção.
  3. Extraia o credentialId do extra da intent, que foi preenchido pelo provedor de credenciais quando a PendingIntent correspondente foi configurada.
  4. Extraia a chave de acesso do banco de dados local usando os parâmetros de solicitação acessados acima.
  5. 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
    )
    
  6. 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.

  7. 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.

  8. Crie uma PublicKeyCredential usando o JSON gerado acima e defina-a em uma GetCredentialResponse 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

  1. Na atividade correspondente, acesse a intent transmitida para onCreate e extraia a ProviderGetCredentialRequest usando o PendingIntentHandler.
  2. 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
        }
    }
    
  3. 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.
}

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>
Diagrama mostrando as funções de mudança e abertura do botão
Figura 1:o botão Change abre a caixa de diálogo de seleção atual, permitindo que o usuário selecione o provedor de credenciais preferido. O botão Open inicia a atividade de configurações definida na mudança de manifesto e abre uma página de configurações especificamente para esse provedor.

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.

Tela de senhas, chaves de acesso e configurações de preenchimento automático
Figura 2:a tela "Passwords, passkeys, and autofill settings".

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.

Diagrama mostrando as funções de mudança e abertura do botão
Figura 3. Tela de serviço preferido para senhas, chaves de acesso e configurações de 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, 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