On Android 15, Credential Manager supports a single tap flow for credential creation and retrieval. In this flow, the information of the credential being created, or being used, is displayed directly in the Biometric Prompt, along with an entrypoint to more options. This simplified process creates a more efficient and streamlined credential creation and retrieval process.
Requirements:
- Biometrics have been set up on the user's device and the user allows them for authentication into applications.
- For sign-in flows, this feature is enabled for single account scenarios only, even if there's multiple credentials (such as passkey and password) available for that account.
Enable single tap on passkey creation flows
This method's creation steps match the existing credential creation
process. Within your BeginCreatePublicKeyCredentialRequest
, use
handleCreatePasskeyQuery()
to process the request if it is for a passkey.
is BeginCreatePublicKeyCredentialRequest -> {
Log.i(TAG, "Request is passkey type")
return handleCreatePasskeyQuery(request, passwordCount, passkeyCount)
}
In your handleCreatePasskeyQuery()
, include BiometricPromptData
with
the CreateEntry
class:
val createEntry = CreateEntry(
// Additional properties...
biometricPromptData = BiometricPromptData(
allowedAuthenticators = allowedAuthenticator
)
)
Credential providers should explicitly set the allowedAuthenticator
property
in the BiometricPromptData
instance. If this property is not set, the value
defaults to DEVICE_WEAK
. Set the optional cryptoObject
property if needed
for your use case.
Enable single tap on sign-in passkey flows
Similar to the passkey creation flow, this will follow the existing setup for
handling user sign-in. Under the BeginGetPublicKeyCredentialOption
, use
populatePasskeyData()
to gather the relevant information about the
authentication request:
is BeginGetPublicKeyCredentialOption -> {
// ... other logic
populatePasskeyData(
origin,
option,
responseBuilder,
autoSelectEnabled,
allowedAuthenticator
)
// ... other logic as needed
}
Similar to CreateEntry
, a BiometricPromptData
instance is set to the
PublicKeyCredentialEntry
instance. If not explicitly set,
allowedAuthenticator
defaults to BIOMETRIC_WEAK
.
PublicKeyCredentialEntry(
// other properties...
biometricPromptData = BiometricPromptData(
allowedAuthenticators = allowedAuthenticator
)
)
Handle credential entry selection
While handling the credential entry selection for passkey creation or
passkey selection during sign in, call the PendingIntentHandler's
retrieveProviderCreateCredentialRequest
, or
retrieveProviderGetCredentialRequest
, as appropriate. These return objects
that contain the metadata needed for the provider. For example, when handling
passkey creation entry selection, update your code as shown:
val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
if (createRequest == null) {
Log.i(TAG, "request is null")
setUpFailureResponseAndFinish("Unable to extract request from intent")
return
}
// Other logic...
val biometricPromptResult = createRequest.biometricPromptResult
// Add your logic based on what needs to be done
// after getting biometrics
if (createRequest.callingRequest is CreatePublicKeyCredentialRequest) {
val publicKeyRequest: CreatePublicKeyCredentialRequest =
createRequest.callingRequest as CreatePublicKeyCredentialRequest
if (biometricPromptResult == null) {
// Do your own authentication flow, if needed
}
else if (biometricPromptResult.isSuccessful) {
createPasskey(
publicKeyRequest.requestJson,
createRequest.callingAppInfo,
publicKeyRequest.clientDataHash,
accountId
)
} else {
val error = biometricPromptResult.authenitcationError
// Process the error
}
// Other logic...
}
This example contains information about the biometric flow's success. It also
contains other information about the credential. If the flow fails, use the
error code under biometricPromptResult.authenticationError
to make decisions.
The error codes returned as part of
biometricPromptResult.authenticationError.errorCode
are the same error codes
defined in the androidx.biometric library, such as
androidx.biometric.BiometricPrompt.NO_SPACE,
androidx.biometric.BiometricPrompt.UNABLE_TO_PROCESS,
androidx.biometric.BiometricPrompt.ERROR_TIMEOUT, and similar. The
authenticationError
will also contain an error message associated with the
errorCode
that can be displayed on a UI.
Similarly, extract metadata during the retrieveProviderGetCredentialRequest
.
Check if your biometric flow is null
. If yes, configure your own biometrics to
authenticate. This is similar to how the get operation is instrumented:
val getRequest =
PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
if (getRequest == null) {
Log.i(TAG, "request is null")
setUpFailureResponseAndFinish("Unable to extract request from intent")
return
}
// Other logic...
val biometricPromptResult = getRequest.biometricPromptResult
// Add your logic based on what needs to be done
// after getting biometrics
if (biometricPromptResult == null)
{
// Do your own authentication flow, if necessary
} else if (biometricPromptResult.isSuccessful) {
Log.i(TAG, "The response from the biometricPromptResult was ${biometricPromptResult.authenticationResult.authenticationType}")
validatePasskey(
publicKeyRequest.requestJson,
origin,
packageName,
uid,
passkey.username,
credId,
privateKey
)
} else {
val error = biometricPromptResult.authenitcationError
// Process the error
}
// Other logic...