Sign in with a passkey

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:

  1. 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.
  2. Create the object required to get the public key credential: Wrap the options sent by the server in a GetPublicKeyCredentialOption object
  3. (optional) Prepare getCredential: In Android 14 and higher, you can reduce latency by showing the account selector by using the prepareGetCredential() method before calling getCredential().
  4. Launch the sign in flow: Call getCredential() method to sign in the user
  5. Handle the response: Handle each of the possible credential responses.
  6. 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)
    }
}

Next steps