El Administrador de credenciales se refiere a un conjunto de APIs que se presentaron en Android 14 y admiten varios métodos de acceso, como nombres de usuario y contraseñas, llaves de acceso y soluciones de acceso federado (como Acceder con Google). Cuando se invoca la API de Credential Manager, el sistema Android agrega las credenciales de todos los proveedores de credenciales que están instalados en el dispositivo. En este documento, se describe el conjunto de APIs que proporcionan extremos de integración para estos proveedores de credenciales.
Configuración
Antes de implementar la funcionalidad en tu proveedor de credenciales, completa los pasos de configuración que se muestran en las siguientes secciones.
Declara las dependencias
En el archivo build.gradle
de tu módulo, declara una dependencia con la versión más reciente de la biblioteca del Administrador de credenciales:
implementation "androidx.credentials:credentials:1.2.0-{latest}"
Declara el elemento de servicio en el archivo de manifiesto
En el archivo AndroidManifest.xml
de manifiesto de tu app, incluye una declaración <service>
para una clase de servicio que extiende la clase CredentialProviderService
de la biblioteca androidx.credentials, como se muestra en el siguiente ejemplo.
<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>
El permiso y el filtro de intents que se muestran arriba son integrales para que el flujo del Administrador de credenciales funcione como se espera. El permiso es necesario para que solo el sistema Android pueda vincularse a este servicio. El filtro de intents se usa para la visibilidad de este servicio como proveedor de credenciales que usará el Administrador de credenciales.
Declara los tipos de credenciales admitidos
En el directorio res/xml
, crea un archivo nuevo llamado provider.xml
. En este archivo, declara los tipos de credenciales que admite tu servicio a través de constantes definidas para cada tipo de credencial en la biblioteca. En el siguiente ejemplo, el servicio admite contraseñas tradicionales así como llaves de acceso, cuyas constantes se definen como TYPE_PASSWORD_CREDENTIAL
y 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>
En niveles anteriores de API, los proveedores de credenciales integran APIs como Autofill para contraseñas y otros datos. Estos proveedores pueden usar la misma infraestructura interna para almacenar los tipos de credenciales existentes, mientras la expanden para admitir otros, incluidas las llaves de acceso.
Enfoque de dos fases en la interacción con el proveedor
El Administrador de credenciales interactúa con los proveedores de credenciales en dos fases:
- La primera fase es la fase de inicio/consulta, en la que el sistema se vincula a los servicios del proveedor de credenciales e invoca a los métodos
onBeginGetCredentialRequest()
,onBeginCreateCredentialRequest()
oonClearCredentialStateRequest()
con solicitudesBegin…
. Los proveedores deben procesar estas solicitudes y devolver respuestasBegin…
. Para ello, deben propagarlas con entradas que representan opciones visuales que se mostrarán en el selector de cuenta. Cada entrada debe tener configurado unPendingIntent
. - Una vez que el usuario selecciona una entrada, comienza la fase de selección y se activa el
PendingIntent
asociado con la entrada, lo que muestra la actividad del proveedor correspondiente. Una vez que el usuario termina de interactuar con esta actividad, el proveedor de credenciales debe establecer la respuesta en el resultado de la actividad antes de finalizarla. Esta respuesta se envía a la app cliente que invocó el Administrador de credenciales.
Controla la creación de llaves de acceso
Controla las consultas para la creación de llaves de acceso
Cuando una app cliente desea crear una llave de acceso y almacenarla con un proveedor de credenciales, llama a la API de createCredential
. Para controlar esta solicitud en tu servicio de proveedor de credenciales de modo que la llave de acceso se almacene en tu almacenamiento, completa los pasos que se muestran en las siguientes secciones.
- Anula el método
onBeginCreateCredentialRequest()
en tu servicio extendido deCredentialProviderService
. - Para controlar la
BeginCreateCredentialRequest
, crea unaBeginCreateCredentialResponse
correspondiente y pásala a través de la devolución de llamada. - Mientras creas la
BeginCreateCredentialResponse
, agrega el objetoCreateEntries
requerido. CadaCreateEntry
debe corresponder a una cuenta en la que se puede guardar la credencial y debe tener unPendingIntent
configurado junto con otros metadatos obligatorios.
En el siguiente ejemplo, se muestra cómo implementar estos pasos.
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
)
)
}
La construcción del PendingIntent
debe cumplir con lo siguiente:
- La Activity correspondiente se debe configurar para mostrar cualquier mensaje, confirmación o selección biométrica requerida.
- Cualquier dato requerido que el proveedor necesite cuando se invoque la actividad correspondiente debe establecerse como un objeto adicional en el intent que se usa para crear tu
PendingIntent
, como unaccountId
en el flujo de creación. - Tu
PendingIntent
debe construirse con la marcaPendingIntent.FLAG_MUTABLE
para que el sistema pueda agregar la solicitud final al intent adicional. - Tu
PendingIntent
no se debe construir con la marcaPendingIntent.FLAG_ONE_SHOT
, ya que el usuario puede seleccionar una entrada, volver atrás y volver a seleccionarla, lo que haría quePendingIntent
se active dos veces. - Tu
PendingIntent
debe construirse con un código de solicitud único para que cada entrada pueda tener suPendingIntent
correspondiente.
Controla la selección de entradas para las solicitudes de creación de llaves de acceso
- Cuando el usuario selecciona un
CreateEntry
propagado con anterioridad, se invoca elPendingIntent
correspondiente y se crea el proveedor asociadoActivity
. - Después de invocar el método
onCreate
de tu Activity, accede al intent asociado y pásalo a la clasePendingIntentHander
para obtener el elementoProviderCreateCredentialRequest
. - Extrae
requestJson
,callingAppInfo
yclientDataHash
de la solicitud. - Extrae el
accountId
local del intent adicional. Esta es una implementación específica de la app de ejemplo y no es obligatoria. Este ID se puede usar para almacenar la credencial con el ID de la cuenta en particular. - Valida el objeto
requestJson
. En el siguiente ejemplo, se usan clases de datos locales, comoPublicKeyCredentialCreationOptions
, para convertir el JSON de entrada en una clase estructurada según la especificación de WebAuthn. Como proveedor de credenciales, puedes reemplazarlo por tu propio analizador. - Verifica el vínculo del recurso de la app que realiza la llamada si esta se origina en una app para Android nativa.
- Muestra un mensaje de autenticación. En el siguiente ejemplo, se usa la API de Biometric de Android.
- Cuando la autenticación se realice de forma correcta, genera un
credentialId
y un par de claves. - Guarda la clave privada en tu base de datos local en
callingAppInfo.packageName
. - Crea una respuesta JSON de la API de Web Authentication que contenga la clave pública y el
credentialId
. En el siguiente ejemplo, se usan clases de utilidad locales, comoAuthenticatorAttestationResponse
yFidoPublicKeyCredential
, que ayudan a construir un JSON según las especificaciones antes mencionadas. Como proveedor de credenciales, puedes reemplazar estas clases con tus propios compiladores. - Crea un objeto
CreatePublicKeyCredentialResponse
con el JSON que se generó antes. - Configura
CreatePublicKeyCredentialResponse
como objeto adicional en unIntent
a través dePendingIntentHander.setCreateCredentialResponse()
y establece ese intent en el resultado de la actividad. - Finaliza la Activity.
En el siguiente ejemplo de código, se ilustran estos pasos. Una vez que se invoque onCreate()
, se deberá controlar este código en la clase de Activity.
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)}"
}
Controla las consultas para las solicitudes de creación de contraseñas
Para controlar las consultas para las solicitudes de creación de contraseñas, haz lo siguiente:
- Dentro del método
processCreateCredentialRequest()
mencionado en la sección anterior, agrega otro caso dentro del bloque de interruptor para controlar las solicitudes de contraseña. - Mientras creas el
BeginCreateCredentialResponse
, agrega el objetoCreateEntries
obligatorio. - Cada
CreateEntry
debe corresponder a una cuenta en la que se puede guardar la credencial y debe tener unPendingIntent
configurado junto con otros metadatos.
En el siguiente ejemplo, se muestra cómo implementar estos pasos:
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)
}
Controla la selección de entradas para las solicitudes de creación de contraseñas
Cuando el usuario selecciona un objeto CreateEntry
propagado, se ejecuta el PendingIntent
correspondiente y se muestra la Activity asociada. Accede al intent asociado que se pasó en onCreate
y pásalo a la clase PendingIntentHander
para obtener el método ProviderCreateCredentialRequest
.
En el siguiente ejemplo, se muestra cómo implementar este proceso. Este código debe controlarse en el método onCreate()
de tu Activity.
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()
Controla el acceso de los usuarios
El acceso de los usuarios se controla siguiendo los pasos que se indican a continuación:
- Cuando una app cliente intenta permitir el acceso de un usuario, prepara una instancia de
GetCredentialRequest
. - El framework de Android propaga esta solicitud a todos los proveedores de credenciales aplicables vinculando estos servicios.
- Luego, el servicio del proveedor recibe una
BeginGetCredentialRequest
que contiene una lista de elementosBeginGetCredentialOption
, cada uno de los cuales contiene parámetros que se pueden usar para recuperar credenciales coincidentes.
Para controlar esta solicitud en tu servicio de proveedor de credenciales, completa los siguientes pasos:
Anula el método
onBeginGetCredentialRequest()
para controlar la solicitud. Ten en cuenta que, si se bloquean las credenciales, puedes establecer unaAuthenticationAction
en la respuesta e invocar la devolución de llamada de inmediato.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()) } }
Los proveedores que requieren el desbloqueo de las credenciales antes de mostrar cualquier
credentialEntries
deben configurar un intent pendiente que lleve al usuario al flujo de desbloqueo de la 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 ) ) }
Recupera las credenciales de tu base de datos local y configúralas con
CredentialEntries
para que se muestren en el selector. En el caso de las llaves de acceso, puedes configurarcredentialId
como un extra en el intent para saber a qué credencial se asigna cuando el usuario selecciona esta 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) }
Consulta las credenciales de tu base de datos, crea llaves de acceso y entradas de contraseña para que se propaguen.
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) ) }
Una vez que consultes y propagues las credenciales, deberás controlar la fase de selección para las credenciales que selecciona el usuario, ya sea una llave de acceso o una contraseña.
Controla la selección del usuario para las llaves de acceso
- En el método
onCreate
de la actividad correspondiente, recupera el intent asociado y pásalo aPendingIntentHandler.retrieveProviderGetCredentialRequest()
. - Extrae el
GetPublicKeyCredentialOption
de la solicitud recuperada anteriormente. Luego, extraerequestJson
yclientDataHash
de esta opción. - Extrae el
credentialId
del intent adicional, que propagó el proveedor de credenciales cuando se configuró elPendingIntent
correspondiente. - Extrae la llave de acceso de la base de datos local con los parámetros de solicitud a los que se accedió más arriba.
Confirma que la llave de acceso es válida con los metadatos extraídos y realiza la verificación del usuario.
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 el usuario, muestra un mensaje biométrico (o algún otro método de aserción). El siguiente fragmento de código usa la API de Android Biometric.
Una vez que la autenticación se realice correctamente, crea una respuesta JSON basada en la especificación de W3 Web Authentication Assertion. En el siguiente fragmento de código, las clases de datos auxiliares, como
AuthenticatorAssertionResponse
, se usan para tomar parámetros estructurados y convertirlos al formato JSON requerido. La respuesta contiene una firma digital de la clave privada de una credencial de WebAuthn. El servidor del usuario de confianza puede verificar esta firma para autenticar a un usuario antes de acceder.Crea una
PublicKeyCredential
con el JSON que se generó antes y establécela en un objetoGetCredentialResponse
final. Establece esta respuesta final en el resultado de esta actividad.
En el siguiente ejemplo, se muestra cómo se pueden implementar estos pasos:
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)
Controla la selección del usuario para la autenticación con contraseña
- En tu actividad correspondiente, accede al intent que se pasó a
onCreate
y extrae el objetoProviderGetCredentialRequest
conPendingIntentHandler
. Usa
GetPasswordOption
en la solicitud para recuperar las credenciales de contraseña para el nombre del paquete entrante.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 } }
Una vez que lo hagas, configura la respuesta para la credencial de contraseña seleccionada.
// Set the response back val result = Intent() val passwordCredential = PasswordCredential(username, password) PendingIntentHandler.setGetCredentialResponse( result, GetCredentialResponse(passwordCredential) ) setResult(Activity.RESULT_OK, result) finish()
Cómo controlar la selección de una entrada de acción de autenticación
Como se mencionó anteriormente, un proveedor de credenciales puede establecer una AuthenticationAction
si las credenciales están bloqueadas. Si el usuario selecciona esta entrada, se invoca la actividad correspondiente a la acción de intent establecida en el PendingIntent
. Luego, los proveedores de credenciales pueden mostrar un flujo de autenticación biométrica o un mecanismo similar para desbloquear las credenciales. Si se ejecuta de forma correcta, el proveedor de credenciales debe crear una BeginGetCredentialResponse
, similar al modo en que se describió antes el control de acceso del usuario, ya que las credenciales ahora están desbloqueadas. Esta respuesta debe establecerse a través del método PendingIntentHandler.setBeginGetCredentialResponse()
antes de que se establezca el intent preparado como resultado y finalice la actividad.
Borra las solicitudes de credenciales
Una app cliente puede solicitar que se borre cualquier estado que se mantenga para la selección de credenciales, como un proveedor de credenciales, que puede recordar la credencial seleccionada antes y mostrarla la próxima vez. Una app cliente llama a esta API y espera que se borre esa selección fija. Tu servicio de proveedor de credenciales puede controlar esta solicitud anulando el método onClearCredentialStateRequest()
:
override fun onClearCredentialStateRequest(
request: android.service.credentials.ClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>,
) {
// Delete any maintained state as appropriate.
}
Agrega la función de crear vínculos a la página de configuración de tu proveedor
Para permitir que los usuarios abran la configuración de tu proveedor desde la pestaña Contraseñas,
llaves de acceso Autocompletar, las apps del proveedor de credenciales deben implementar
Atributo credential-provider
settingsActivity
del manifiesto en
res/xml/provider.xml
Este atributo te permite usar un intent para abrir la interfaz de usuario
pantalla de configuración propia si un usuario hace clic en el nombre de un proveedor en la pestaña Contraseñas,
llaves de acceso Autocompletar. Establece el valor de este atributo según
nombre de la actividad que se iniciará desde la pantalla de configuración.
<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 configuración
Abrir configuración: El intent android.settings.CREDENTIAL_PROVIDER
abre una pantalla de configuración en la que el usuario puede seleccionar sus proveedores de credenciales preferidos y adicionales.
Servicio de credenciales preferido: Es el
El intent ACTION_REQUEST_SET_AUTOFILL_SERVICE
redirecciona al usuario al
pantalla de selección del proveedor preferido. El proveedor seleccionado en esta pantalla
se convierte en las credenciales preferidas y en el proveedor de Autocompletar.
Obtén una lista de apps con privilegios permitidas
Las apps con privilegios, como los navegadores web, realizan llamadas al Administrador de credenciales en nombre de otros usuarios de confianza si se configura el parámetro origin
en GetCredentialRequest()
y CreatePublicKeyCredentialRequest()
del Administrador de credenciales. Para procesar estas solicitudes, el proveedor de credenciales recupera el origin
usando la API de getOrigin()
.
Para recuperar el origin
, la app del proveedor de credenciales debe pasar una lista de emisores con privilegios y de confianza a la API de androidx.credentials.provider.CallingAppInfo's getOrigin()
. Esta lista de entidades permitidas debe ser un objeto JSON válido. Se muestra el origin
si el packageName
y las huellas digitales del certificado obtenidas de signingInfo
coinciden con las de una app que se encuentra en la privilegedAllowlist
que se pasa a la API de getOrigin()
. Después de obtener el valor de origin
, la app del proveedor debe considerarla una llamada con privilegios y establecer este origin
en los datos del cliente en la AuthenticatorResponse
, en lugar de calcular el origin
con la firma de la app que realiza la llamada.
Si recuperas un origin
, usa el clientDataHash
que se proporciona directamente en CreatePublicKeyCredentialRequest()
o GetPublicKeyCredentialOption()
, en lugar de organizar clientDataJSON
y generarle un hash durante la solicitud de firma. Para evitar problemas de análisis de JSON, configura un valor de marcador de posición para clientDataJSON
en la respuesta de certificación y aserción.
El Administrador de contraseñas de Google usa una lista de entidades permitidas disponible para
llamadas a getOrigin()
. Como proveedor de credenciales, puedes usar esta lista o proporcionar la tuya en el formato JSON que describe la API. Depende del proveedor seleccionar qué lista se usará. Para obtener acceso privilegiado con proveedores de credenciales externos, consulta la documentación proporcionada por el tercero.
Habilita proveedores en un dispositivo
Los usuarios deben habilitar el proveedor a través de las opciones configuración del dispositivo > cuentas y contraseñas > tu proveedor > habilitar o inhabilitar.
fun createSettingsPendingIntent(): PendingIntent