Revision 2025 Q4: Learn how to simplify auth journeys using Credential Manager API in your Android app

1. Before you begin

Traditional authentication solutions pose a number of security and usability challenges.

Passwords are widely used but...

  • Easily forgotten
  • Users require knowledge to create strong passwords.
  • Easy to phish, harvest and replay by attackers.

Android has worked towards creating Credential Manager API to simplify the sign-in experience and address security risks by supporting passkeys, the next generation industry standard for passwordless authentication.

Credential Manager brings together support for passkeys and combines it with traditional authentication methods such as passwords, Sign in with Google etc.

Users will be able to create passkeys, store them in Google Password Manager, which will sync those passkeys across the Android devices where the user is signed in. A passkey has to be created, associated with a user account, and have its public key stored on a server before a user can sign in with it.

In this codelab, you will learn how to sign up using passkeys and password using Credential Manager API and use them for future authentication purposes. There are 2 flows including:

  • Sign up : using passkeys and password.
  • Sign in : using passkeys & saved password.

Prerequisites

  • Basic understanding of how to run apps in Android Studio.
  • Basic understanding of authentication flow in Android apps.
  • Basic understanding of passkeys.

What you'll learn

  • How to create a passkey.
  • How to save password in password manager.
  • How to authenticate users with a passkey or saved password.

What you'll need

One of the following device combinations:

  • An Android device that runs Android 9 or higher (for passkeys) and Android 4.4 or higher(for password authentication through Credential Manager API).
  • Device preferably with a biometric sensor.
  • Make sure to register a screen lock (biometric or otherwise).
  • Kotlin plugin version : 1.8.10

2. Get set up

This sample app requires a digital asset linking to a website for Credential Manager to validate the linking and proceed further, so the rp id used in the mock responses is from a mocked 3P server. If you want to try your own mock response, try adding your app domain and don't forget to complete the digital asset linking as mentioned here.

Use the same debug.keystore mentioned in the project to build debug and release variants to verify the digital asset linking of the package name and sha on your mock server. (This is already being done for you for the sample app in build.gradle).

  1. Clone this repo on your laptop from credman_codelab branch: https://github.com/android/identity-samples/tree/credman_codelab
git clone -b credman_codelab https://github.com/android/identity-samples.git
  1. Go to the CredentialManager module and open the project in Android Studio.

Lets see app's initial state

To see how the initial state of the app works, follow these steps:

  1. Launch the app.
  2. You see a main screen with a sign up and sign in button. These buttons don't do anything yet, but we'll enable their functionality in the upcoming sections.

7a6fe80f4cf877a8.jpeg

3. Add the ability to sign up using passkeys

When signing up for a new account on an Android app that uses the Credential Manager API, users can create a passkey for their account. This passkey will be securely stored on the user's chosen credential provider and used for future sign-ins, without requiring the user to enter their password each time.

Now, you will create a passkey and register user credentials using biometrics/screen lock.

Sign up with passkey

The code inside CredentialManager/app/src/main/java/com/google/credentialmanager/sample/SignUpScreen.kt defines a text field "username" and a button to sign up with a passkey.

1f4c50daa2551f1.jpeg

Define createCredential() lambda for use in View Models

Credential manager objects require an Activity to be passed in, which is associated with a Screen. However, Credential manager operations are usually triggered in View Models, and it's not recommended to reference Activities within View Models. Therefore, we define Credential manager functions in a separate file CredentialManagerUtil.kt and reference them in the appropriate Screens, which then pass them to their View Models as callbacks through lambda functions.

Locate the TODO comment in the createCredential() function in CredentialManagerUtil.kt and call the CredentialManager.create() function:

CredentialManagerUtil.kt

suspend fun createCredential(
    activity: Activity,
    request: CreateCredentialRequest
): CreateCredentialResponse {
    TODO("Create a CredentialManager object and call createCredential() with a CreateCredentialRequest")
    val credentialManager = CredentialManager.create(activity)
    return credentialManager.createCredential(activity, request)
}

Pass the challenge and other json response to a createPasskey() call

Before a passkey is created, you need to request from the server the necessary information to be passed to the Credential Manager API during createCredential() call.

You already have a mock response in your project's assets, called RegFromServer.txt, which returns the necessary parameters in this codelab.

  • In your app, navigate to the SignUpViewModel.kt, Find the signUpWithPasskeys method where you will write the logic for creating a passkey and letting the user in. You can find the method in the same class.
  • Locate the TODO comment block to create a CreatePublicKeyCredentialRequest() and replace with the following code:

SignUpViewModel.kt

TODO("Create a CreatePublicKeyCredentialRequest() with necessary registration json from server")
    val request = CreatePublicKeyCredentialRequest(
        jsonProvider.fetchRegistrationJson()
            .replace("<userId>", getEncodedUserId())
            .replace("<userName>", _username.value)
            .replace("<userDisplayName>", _username.value)
            .replace("<challenge>", getEncodedChallenge())
    )

The jsonProvider.fetchRegistrationJsonFromServer() method reads an emulated server PublicKeyCredentialCreationOptions JSON response from assets and returns the registration JSON to be passed while creating the passkey. We replace some of the placeholder values with user entries from our app and some mocked fields:

  • This JSON is incomplete and has 4 fields that need to be replaced.
  • UserId needs to be unique so that a user can create multiple passkeys (if required). Replace <userId> with the generated userId value.
  • <challenge> also needs to be unique so you will be generating a random unique challenge. The method is already in your code.

A real server PublicKeyCredentialCreationOptions response may return more options. An example of some of these fields is given below:

{
  "challenge": String,
  "rp": {
    "name": String,
    "id": String
  },
  "user": {
    "id": String,
    "name": String,
    "displayName": String
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

The following table explains some of the important parameters in a PublicKeyCredentialCreationOptions object:

Parameters

Descriptions

challenge

A server-generated random string that contains enough entropy to make guessing it infeasible. It should be at least 16 bytes long. This is required but unused during registration unless doing attestation.

user.id

A user's unique ID. This value must not include personally identifying information, for example, e-mail addresses or usernames. A random, 16-byte value generated per account will work well.

user.name

This field should hold a unique identifier for the account that the user will recognise, like their email address or username. This will be displayed in the account selector. (If using a username, use the same value as in password authentication.)

user.displayName

This field is an optional, more user-friendly name for the account.

rp.id

The Relying Party Entity corresponds to your application details. It has the following attributes:

  • name (required): your application name
  • ID (optional): corresponds to the domain or subdomain. If absent, the current domain is used.
  • icon (optional).

pubKeyCredParams

List of allowed algorithms and key types. This list must contain at least one element.

excludeCredentials

The user trying to register a device may have registered other devices. To limit the creation of multiple credentials for the same account on a single authenticator, you can then ignore these devices. The transports member, if provided, should contain the result of calling getTransports() during the registration of each credential.

authenticatorSelection.authenticatorAttachment

Indicates if the device should be attached on the platform, or not or if there is no requirement to do so. Set this value to platform. This indicates that you want an authenticator that is embedded into the platform device, and the user will not be prompted to insert e.g. a USB security key.

residentKey

indicate the value required to create a passkey.

Create a credential

  1. Once you create a CreatePublicKeyCredentialRequest(), you need to call the createCredential() call with the created request.

SignUpViewModel.kt

try {
   TODO("Call createCredential() with createPublicKeyCredentialRequest")
   createCredential(request)
   TODO("Complete the registration process after sending public key credential to your server and let the user in")

} catch (e: CreateCredentialException) {
   handlePasskeyFailure(e)
}

  • You handle the rendered views visibility and handle the exceptions if the request fails or unsuccessful due to some reason. Here the error messages are logged and shown on the app in an error dialog. You can check the full error logs through Android studio or the adb debug command.

1ea8ace66135de1e.png

  1. Finally, you need to complete the registration process. The app sends a public key credential to the server which registers it to the current user.

Here, we have used a mock server, so we just return true indicating that server has saved the registered public key for future authentication and validation purposes. You can read more about server-side passkey registration for your own implementation.

Inside signUpWithPasskeys() method, find relevant comment and replace with following code:

SignUpViewModel.kt

try {
    createCredential(request)
    TODO("Complete the registration process after sending public key credential to your server and let the user in")
registerResponse()
    DataProvider.setSignedInThroughPasskeys(true)
    _navigationEvent.emit(NavigationEvent.NavigateToHome(signedInWithPasskeys = true))
} catch (e: CreateCredentialException) {
   handlePasskeyFailure(e)
}
  • registerResponse() returns true indicating the mock server has saved the public key for future use.
  • Set setSignedInThroughPasskeys flag to true.
  • Once logged in, you redirect your user to the home screen.

A real PublicKeyCredential may contain more fields. An example of these fields is shown below:

{
  "id": String,
  "rawId": String,
  "type": "public-key",
  "response": {
    "clientDataJSON": String,
    "attestationObject": String,
  }
}

The following table explains some of the important parameters in a PublicKeyCredential object:

Parameters

Descriptions

id

A Base64URL encoded ID of the created passkey. This ID helps the browser determine whether a matching passkey is in the device upon authentication. This value must be stored in the database on the backend.

rawId

An ArrayBuffer object version of credential ID.

response.clientDataJSON

An ArrayBuffer object encoded client data.

response.attestationObject

An ArrayBuffer encoded attestation object. It contains important information, such as an RP ID, flags, and a public key.

Run the app, and you will be able to click on the Sign up with passkeys button and create a passkey.

4. Save a password in Credential Provider

In this app, inside your SignUp screen, you already have a sign up with username and password implemented for demonstration purposes.

To save the user password credential with their password provider, you will implement a CreatePasswordRequest to pass to createCredential() to save the password.

  • Find signUpWithPassword() method, replace the TODO with a createPassword call:

SignUpViewModel.kt

TODO("CreatePasswordRequest with entered username and password")
    val passwordRequest = CreatePasswordRequest(_username.value, _password.value)

  • Next, create a credential with a create password request and save the user password credential with their password provider. Then, log the user in. We catch exceptions that occur in this flow more generically. Replace the TODO with following code:

SignUpViewModel.kt

TODO("Create credential with created password request and log the user in")
    try {
        createCredential(passwordRequest)
        simulateServerDelayAndLogIn()
    } catch (e: Exception) {
        val errorMessage = "Exception Message : " + e.message
        Log.e("Auth", errorMessage)