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 biometric (or screen lock).
- Kotlin plugin version : 1.8.10
2. Get set up
- Clone this repo on your laptop from credman_codelab branch : https://github.com/android/identity-samples/tree/credman_codelab
- 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:
- Launch the app.
- You see a main screen with a sign up and sign in button.
- You can click sign up to sign up using a passkey or a password.
- You can click sign in to sign in using passkey & saved password.
To understand what passkeys are and how they work, see How do passkeys work? .
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
Inside Credential Manager -> app -> main -> java -> SignUpFragment.kt, you can see a text field "username" and a button to sign up with passkey.
Pass the challenge and other json response to createPasskey() call
Before a passkey is created, you need to request the server to get necessary information to be passed to the Credential Manager API during createCredential() call.
Luckily, you already have a mock response in your assets(RegFromServer.txt) which returns such parameters in this codelab.
- In your app, navigate to the SignUpFragment.kt, Find, 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.
- Check the else block with a comment to call createPasskey() and replace with following code :
SignUpFragment.kt
//TODO : Call createPasskey() to signup with passkey
val data = createPasskey()
This method will be called once you have a valid username filled on the screen.
- Inside the createPasskey() method, you need to create a CreatePublicKeyCredentialRequest() with necessary params returned.
SignUpFragment.kt
//TODO create a CreatePublicKeyCredentialRequest() with necessary registration json from server
val request = CreatePublicKeyCredentialRequest(fetchRegistrationJsonFromServer())
This fetchRegistrationJsonFromServer() is a method, which reads registration json response from assets and returns the registration json to be passed while creating passkey.
- Find fetchRegistrationJsonFromServer() method and replace the TODO with following code to return json and also remove the empty string return statement :
SignUpFragment.kt
//TODO fetch registration mock response
val response = requireContext().readFromAsset("RegFromServer")
//Update userId,challenge, name and Display name in the mock
return response.replace("<userId>", getEncodedUserId())
.replace("<userName>", binding.username.text.toString())
.replace("<userDisplayName>", binding.username.text.toString())
.replace("<challenge>", getEncodedChallenge())
- Here, you read the registration json from assets.
- This json has 4 fields to be replaced.
- UserId needs to be unique so that a user can create multiple passkeys (if required). You replace <userId> with the userId generated.
- <challenge> also needs to be unique so you will be generating a random unique challenge. The method is already in your code.
The following code snippet includes sample options that you receive from the server:
{
"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 isn't exhaustive, but it contains the important parameters in the PublicKeyCredentialCreationOptions
dictionary:
Parameters | Descriptions |
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. | |
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. | |
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.) | |
This field is an optional, more user-friendly name for the account. It's a human-palatable name for the user account, intended only for display. | |
The Relying Party Entity corresponds to your application details.It needs :
| |
The Public Key Credential Parameters is a list of allowed algorithms and key types. This list must contain at least one element. | |
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. | |
indicates if the device should be attached on the platform or not or if there is no requirement about it. Set it to "platform". This indicates that we 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. | |
| indicate a value "required" to create a passkey. |
Create a credential
- Once you create a CreatePublicKeyCredentialRequest(), you need to call the createCredential() call with the created request.
SignUpFragment.kt
//TODO call createCredential() with createPublicKeyCredentialRequest
try {
response = credentialManager.createCredential(
requireActivity(),
request
) as CreatePublicKeyCredentialResponse
} catch (e: CreateCredentialException) {
configureProgress(View.INVISIBLE)
handlePasskeyFailure(e)
}
- You pass the required information to createCredential().
- Once the request is successful, you would see a bottomsheet on your screen prompting you to create a passkey.
- Now users can verify their identity through biometrics or screen lock etc.
- 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 adb debug command
- Now, finally you need to complete the registration process by sending the public key credential to the server and letting the user in. The app receives a credential object that contains a public key that you can send to the server to register the passkey.
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.
Inside signUpWithPasskeys() method, find relevant comment and replace with following code:
SignUpFragment.kt
//TODO : complete the registration process after sending public key credential to your server and let the user in
data?.let {
registerResponse()
DataProvider.setSignedInThroughPasskeys(true)
listener.showHome()
}
- registerResponse return true indicating (mock) server has saved the public key for future use.
- You setSignedInThroughPasskeys flag as true, indicating that you are logging in through passkeys.
- Once logged in, you redirect your user to the home screen.
The following code snippet contains an example options you should receive:
{
"id": String,
"rawId": String,
"type": "public-key",
"response": {
"clientDataJSON": String,
"attestationObject": String,
}
}
The following table isn't exhaustive, but it contains the important parameters in PublicKeyCredential
:
Parameters | Descriptions |
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. | |
An | |
An | |
An |
Run the app, and you will be able to click on the Sign up with passkeys button and create a passkey.
4. Save 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 to createPassword call :
SignUpFragment.kt
//TODO : Save the user credential password with their password provider
createPassword()
- Inside createPassword() method, you need to create password request like this, replace the TODO with following code :
SignUpFragment.kt
//TODO : CreatePasswordRequest with entered username and password
val request = CreatePasswordRequest(
binding.username.text.toString(),
binding.password.text.toString()
)
- Next, inside createPassword() method, you need to create credential with create password request and save the user password credential with their password provider, replace the TODO with following code :
SignUpFragment.kt
//TODO : Create credential with created password request
try {
credentialManager.createCredential(request, requireActivity()) as CreatePasswordResponse
} catch (e: Exception) {
Log.e("Auth", " Exception Message : " + e.message)
}
- Now you have successfully saved the password credential with the user's password provider to authenticate via password in just one-tap.
5. Add the ability to authenticate with a passkey or password
Now you are ready to use it as a way to authenticate to your app safely.
Obtain the challenge and other options to pass to getPasskey() call
Before you ask the user to authenticate, you need to request parameters to pass in WebAuthn json from the server, including a challenge.
You already have a mock response in your assets(AuthFromServer.txt) which returns such parameters in this codelab.
- In your app, navigate to the SignInFragment.kt, find
signInWithSavedCredentials
method where you will write the logic for authenticating through saved passkey or password and let the user in : - Check the else block with a comment to call createPasskey() and replace with following code :
SignInFragment.kt
//TODO : Call getSavedCredentials() method to signin using passkey/password
val data = getSavedCredentials()
- Inside the getSavedCredentials() method, you need to create a
GetPublicKeyCredentialOption
() with necessary parameters required to get credentials from your credential provider.
SigninFragment.kt
//TODO create a GetPublicKeyCredentialOption() with necessary registration json from server
val getPublicKeyCredentialOption =
GetPublicKeyCredentialOption(fetchAuthJsonFromServer(), null)
This fetchAuthJsonFromServer() is a method, which reads authentication json response from assets and returns the authentication json to retrieve all the passkeys associated with this user account.
2nd parameter : clientDataHash - a hash that is used to verify the relying party identity, set only if you have set the GetCredentialRequest.origin. For the sample app, this is null.
The 3rd parameter is true if you prefer the operation to return immediately when there is no available credential instead of falling back to discovering remote credentials, and false (default) otherwise.
- Find fetchAuthJsonFromServer() method and replace the TODO with following code to return json and also remove the empty string return statement :
SignInFragment.kt
//TODO fetch authentication mock json
return requireContext().readFromAsset("AuthFromServer")
Note : This codelab's server is designed to return a JSON that is as similar as possible to the PublicKeyCredentialRequestOptions
dictionary that's passed to API's getCredential() call. The following code snippet includes a few example options that you should receive:
{
"challenge": String,
"rpId": String,
"userVerification": "",
"timeout": 1800000
}
The following table isn't exhaustive, but it contains the important parameters in the PublicKeyCredentialRequestOptions
dictionary:
Parameters | Descriptions |
A server-generated challenge in an | |
An RP ID is a domain. A website can specify either its domain or a registrable suffix. This value must match the |
- Next you need to create a PasswordOption() object to retrieve all the saved passwords saved in your password provider through the Credential Manager API for this user account. Inside getSavedCredentials() method, find the TODO and replace with the following :
SigninFragment.kt
//TODO create a PasswordOption to retrieve all the associated user's password
val getPasswordOption = GetPasswordOption()
Get credentials
- Next you need to call getCredential() request with all the above options to retrieve the associated credentials :
SignInFragment.kt
//TODO call getCredential() with required credential options
val result = try {
credentialManager.getCredential(
requireActivity(),
GetCredentialRequest(
listOf(
getPublicKeyCredentialOption,
getPasswordOption
)
)
)
} catch (e: Exception) {
configureViews(View.INVISIBLE, true)
Log.e("Auth", "getCredential failed with exception: " + e.message.toString())
activity?.showErrorAlert(
"An error occurred while authenticating through saved credentials. Check logs for additional details"
)
return null
}
if (result.credential is PublicKeyCredential) {
val cred = result.credential as PublicKeyCredential
DataProvider.setSignedInThroughPasskeys(true)
return "Passkey: ${cred.authenticationResponseJson}"
}
if (result.credential is PasswordCredential) {
val cred = result.credential as PasswordCredential
DataProvider.setSignedInThroughPasskeys(false)
return "Got Password - User:${cred.id} Password: ${cred.password}"
}
if (result.credential is CustomCredential) {
//If you are also using any external sign-in libraries, parse them here with the utility functions provided.
}
- You pass the required information to getCredential(). This takes the list of credential options and an activity context to render the options in the bottomsheet in that context.
- Once the request is successful, you would see a bottomsheet on your screen listing all the created credentials for the associated account.
- Now users can verify their identity through biometrics or screen lock etc to authenticate the chosen credential.
- You setSignedInThroughPasskeys flag as true, indicating that you are logging in through passkeys. Otherwise false.
- 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 adb debug command
- Now, finally you need to complete the registration process by sending the public key credential to the server and letting the user in. The app receives a credential object that contains a public key that you can send to the server to authenticate via the passkey.
Here, we have used a mock server, so we just return true indicating that server has validated the public key.
Inside signInWithSavedCredentials
() method, find relevant comment and replace with following code:
SignInFragment.kt
//TODO : complete the authentication process after validating the public key credential to your server and let the user in.
data?.let {
sendSignInResponseToServer()
listener.showHome()
}
- sendSigninResponseToServer() returns true indicating (mock) server has validated the public key for future use.
- Once logged in, you redirect your user to the home screen.
The following code snippet includes an example PublicKeyCredential
object:
{
"id": String
"rawId": String
"type": "public-key",
"response": {
"clientDataJSON": String
"authenticatorData": String
"signature": String
"userHandle": String
}
}
The following table isn't exhaustive, but it contains the important parameters in the PublicKeyCredential
object:
Parameters | Descriptions |
The Base64URL encoded ID of the authenticated passkey credential. | |
An | |
An | |
An | |
An | |
An |
Run the app, navigate to sign in -> Sign in with passkeys/saved password and try signing in using saved credentials.
Try it
You implemented the creation of passkeys, saving password in Credential Manager, and authentication through passkeys or saved password using Credential Manager API on your Android app.
6. Congratulations!
You finished this codelab! If you want to check the final resolution, it is available at https://github.com/android/identity-samples/tree/main/CredentialManager
If you have any questions, ask them on StackOverflow with a passkey
tag.