This guide continues on the implementation of using passkeys for authentication. Before your users can sign in with passkeys, you must also complete the instructions in Create passkeys.
To authenticate with a passkey, you must first retrieve the options required to retrieve the public key from your app server, and then call the Credential Manager API to retrieve the public key. Then, handle the sign-in response appropriately.
Overview
This guide focuses on the changes required in your client app to sign in your user with a passkey, and gives a brief overview of the app server-side implementation. To learn more about server-side integration, see Server-side passkey authentication.
To retrieve all the passkey and password options that are associated with the user's account, complete these steps:
- Get credential request options from the server: Make a request from your app to your authentication server to start the passkey sign-in process. From the server, send the options required to get the public key credential, as well as a unique challenge.
- Create the object required to get the public key credential: Wrap
the options sent by the server in a
GetPublicKeyCredentialOptionobject - (optional) Prepare getCredential: In Android 14 and higher, you can
reduce latency by showing the account selector by using the
prepareGetCredential()method before callinggetCredential(). - Launch the sign in flow: Call
getCredential()method to sign in the user - Handle the response: Handle each of the possible credential responses.
- Handle exceptions: Make sure that you handle exceptions appropriately.
1. Get credential request options from the server
Request the server for the options required to get the public key credentials,
as well as the challenge, which is unique for each sign-in attempt. To learn
more about the server-side implementation, see Create the
challenge and Create credential request
options.
The options look similar to the following:
{
"challenge": "<your app challenge>",
"allowCredentials": [],
"rpId": "<your app server domain>"
}
To learn more about the fields, see the blogpost about signing in with a passkey.
2. Create the object required to get the public key credential
In your app, use the options to create a GetPublicKeyCredentialOption object.
In the following example, requestJson represents the options sent by the
server.
// Get password logins from the credential provider on the user's device.
val getPasswordOption = GetPasswordOption()
// Get passkeys from the credential provider on the user's device.
val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
requestJson = requestJson
)
Then, wrap GetPublicKeyCredentialOption in a GetCredentialRequest object.
val credentialRequest = GetCredentialRequest(
// Include all the sign-in options that your app supports.
listOf(getPasswordOption, getPublicKeyCredentialOption),
// Defines whether you prefer to use only immediately available
// credentials or hybrid credentials.
preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
)
3. Optional: Reduce sign-in latency
On Android 14 or higher, you can reduce latency when showing the account
selector by using the prepareGetCredential() method before calling
getCredential().
The prepareGetCredential() method returns a
PrepareGetCredentialResponse object which is cached. This lets the
getCredential() method in the following step bring up the account selector
with the cached data.
coroutineScope {
val response = credentialManager.prepareGetCredential(
GetCredentialRequest(
listOf(
// Include all the sign-in options that your app supports
getPublicKeyCredentialOption,
getPasswordOption
)
)
)
}
4. Launch the sign-in flow
Call the getCredential() method to show the user the account selector. Use the
following code snippet as a reference for how to launch the sign-in flow:
coroutineScope {
try {
result = credentialManager.getCredential(
// Use an activity-based context to avoid undefined system UI
// launching behavior.
context = activityContext,
request = credentialRequest
)
handleSignIn(result)
} catch (e: GetCredentialException) {
// Handle failure
}
}
5. Handle the response
Handle the response, which can contain one of various types of credential objects.
fun handleSignIn(result: GetCredentialResponse) {
// Handle the successfully returned credential.
val credential = result.credential
when (credential) {
is PublicKeyCredential -> {
val responseJson = credential.authenticationResponseJson
// Share responseJson i.e. a GetCredentialResponse on your server to
// validate and authenticate
}
is PasswordCredential -> {
val username = credential.id
val password = credential.password
// Use id and password to send to your server to validate
// and authenticate
}
is CustomCredential -> {
// If you are also using any external sign-in libraries, parse them
// here with the utility functions provided.
if (credential.type == ExampleCustomCredential.TYPE) {
try {
val ExampleCustomCredential =
ExampleCustomCredential.createFrom(credential.data)
// Extract the required credentials and complete the authentication as per
// the federated sign in or any external sign in library flow
} catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) {
// Unlikely to happen. If it does, you likely need to update the dependency
// version of your external sign-in library.
Log.e(TAG, "Failed to parse an ExampleCustomCredential", e)
}
} else {
// Catch any unrecognized custom credential type here.
Log.e(TAG, "Unexpected type of credential")
}
}
else -> {
// Catch any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential")
}
}
}
The PublicKeyCredential returned from authentication is essentially a signed
assertion, structured as follows:
{
"id": "<credential ID>",
"type": "public-key",
"rawId": "<raw credential ID>",
"response": {
"clientDataJSON": "<signed client data containing challenge>",
"authenticatorData": "<authenticator metadata>",
"signature": "<digital signature to be verified>",
"userHandle": "<user ID from credential registration>"
}
}
On the server, you must verify the credential. To learn more, see Verify and sign in the user.
6. Handle exceptions
You should handle all the subclass exceptions of GetCredentialException.
To learn how to handle each exception, see the troubleshooting guide.
coroutineScope {
try {
result = credentialManager.getCredential(
context = activityContext,
request = credentialRequest
)
} catch (e: GetCredentialException) {
Log.e("CredentialManager", "No credential available", e)
}
}