Credential Manager is a Jetpack API that supports multiple sign-in methods, such as username and password, passkeys, and federated sign-in solutions (such as Sign-in with Google) in a single API, thus simplifying the integration for developers.
Furthermore, for users, Credential Manager unifies the sign-in interface across authentication methods, making it clearer and easier for users to sign into apps, regardless of the method they choose.
This page explains the concept of passkeys and the steps to implementing client-side support for authentication solutions, including passkeys, using the Credential Manager API. There is also a separate FAQ page that provides answers to more detailed, specific questions.
Your feedback is a crucial part of improving the Credential Manager API. Share any issues you find or ideas for improving the API using the following link:
About passkeys
Passkeys are a safer and easier replacement for passwords. With passkeys, users can sign in to apps and websites using a biometric sensor (such as a fingerprint or facial recognition), PIN, or pattern. This provides a seamless sign-in experience, freeing your users from having to remember usernames or passwords.
Passkeys rely on WebAuthn (Web Authentication), a standard jointly developed by the FIDO Alliance and the World Wide Web Consortium (W3C). WebAuthn uses public-key cryptography to authenticate the user. The website or app that the user is signing into can see and store the public key, but never the private key. The private key is kept secret and safe. And because the key is unique and tied to the website or app, passkeys are un-phishable, adding further security.
Credential Manager allows users to create passkeys and store them in Google Password Manager.
Prerequisities
To use Credential Manager, complete the steps in this section.
Use a recent platform version
Credential Manager is supported on Android 4.4 (API level 19) and higher.
Add dependencies to your app
Add the following dependencies to your app module's build script:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.0.0-alpha08") // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation("androidx.credentials:credentials-play-services-auth:1.0.0-alpha08") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.0.0-alpha08" // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation "androidx.credentials:credentials-play-services-auth:1.0.0-alpha08" }
Preserve classes in ProGuard file
In your module's proguard-rules.pro
file, add the following directives:
-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
*;
}
Learn more about how to shrink, obfuscate, and optimize your app.
Add support for Digital Asset Links
To enable support for passkeys for your Android app, associate your app with a website that your app owns. You can declare this association by completing the following steps:
Create a Digital Asset Links JSON file. For example, to declare that the website
https://signin.example.com
and an Android app with the package namecom.example
can share sign-in credentials, create a file namedassetlinks.json
with the following content:[{ "relation": ["delegate_permission/common.get_login_creds"], "target": { "namespace": "web", "site": "https://signin.example.com" } }, { "relation": ["delegate_permission/common.get_login_creds"], "target": { "namespace": "android_app", "package_name": "com.example", "sha256_cert_fingerprints": [ SHA_HEX_VALUE ] } }]
The
relation
field is an array of one or more strings that describe the relationship being declared. To declare that apps and sites share sign-in credentials, specify the stringdelegate_permission/common.get_login_creds
.The
target
field is an object that specifies the asset the declaration applies to. The following fields identify a website:namespace
web
site
The website's URL, in the format
https://domain[:optional_port]
; for example,https://www.example.com
.The domain must be fully-qualified., and optional_port must be omitted when using port 443 for HTTPS.
A
site
target can only be a root domain: you cannot limit an app association to a specific subdirectory. Don't include a path in the URL, such as a trailing slash.Subdomains are not considered to match: that is, if you specify the domain as
www.example.com
, the domainwww.counter.example.com
is not associated with your app.The following fields identify an Android app:
namespace
android_app
package_name
The package name declared in the app's manifest. For example, com.example.android
sha256_cert_fingerprints
The SHA256 fingerprints of your app's signing certificate. Host the Digital Assets Link JSON file at the following location on the sign-in domain:
https://domain[:optional_port]/.well-known/assetlinks.json
For example, if your sign-in domain is
signin.example.com
, host the JSON file athttps://signin.example.com/.well-known/assetlinks.json
.The MIME type for the Digital Assets Link file needs to be JSON. Make sure the server sends a
Content-Type: application/json
header in the response.Ensure that your host permits Google to retrieve your Digital Asset Link file. If you have a
robots.txt
file, it must allow the Googlebot agent to retrieve/.well-known/assetlinks.json
. Most sites can simply allow any automated agent to retrieve files in the/.well-known/
path so that other services can access the metadata in those files:User-agent: * Allow: /.well-known/
Declare the association in the Android app:
Add an
asset_statements
string resource to thestrings.xml
file. Theasset_statements
string is a JSON object that specifies theassetlinks.json
files to load. You must escape any apostrophes and quotation marks you use in the string. For example:<string name="asset_statements" translatable="false"> [{ \"include\": \"https://signin.example.com/.well-known/assetlinks.json\" }] </string>
> GET /.well-known/assetlinks.json HTTP/1.1 > User-Agent: curl/7.35.0 > Host: signin.example.com < HTTP/1.1 200 OK < Content-Type: application/json
Configure the Credential Manager
To configure and initialize a CredentialManager
object, add logic similar to
the following:
Kotlin
// Use your app or activity context to instantiate a client instance of // CredentialManager. val credentialManager = CredentialManager.create(context)
Java
// Use your app or activity context to instantiate a client instance of // CredentialManager. CredentialManager credentialManager = CredentialManager.create(context)
Sign in your user
To retrieve all the passkeys and password options that are associated with the user's account, complete these steps:
Initialize the password and passkey authentication options:
Kotlin
// Retrieves the user's saved password for your app from their // password provider. val getPasswordOption = GetPasswordOption() // Get passkeys from the user's public key credential provider. val getPublicKeyCredentialOption = GetPublicKeyCredentialOption( requestJson = requestJson, preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials )
Java
// Retrieves the user's saved password for your app from their // password provider. GetPasswordOption getPasswordOption = new GetPasswordOption(); // Get passkeys from the user's public key credential provider. GetPublicKeyCredentialOption getPublicKeyCredentialOption = new GetPublicKeyCredentialOption(requestJson, preferImmediatelyAvailableCredentials);
Build the sign-in request, using the options retrieved from the previous step:
Kotlin
val getCredRequest = GetCredentialRequest( listOf(getPasswordOption, getPublicKeyCredentialOption) )
Java
GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder() .addCredentialOption(getPasswordOption) .addCredentialOption(getPublicKeyCredentialOption) .build();
Launch the sign-in flow:
Kotlin
coroutineScope.launch { try { val result = credentialManager.getCredential( request = getCredRequest, activity = activity, ) handleSignIn(result) } catch (e : GetCredentialException) { handleFailure(e) } } fun handleSignIn(result: GetCredentialResponse) { // Handle the successfully returned credential. val credential = result.credential when (credential) { is PublicKeyCredential -> { responseJson = credential.authenticationResponseJson fidoAuthenticateWithServer(responseJson) } is PasswordCredential -> { val username = credential.id val password = credential.password passwordAuthenticateWithServer(username, password) } else -> { // Catch any unrecognized credential type here. Log.e(TAG, "Unexpected type of credential") } } }
Java
credentialManager.getCredentialAsync( getCredRequest, activity, cancellationSignal, requireContext().getMainExecutor(), new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() { @Override public void onResult(GetCredentialResponse result) { // Handle the successfully returned credential. Credential credential = result.getCredential(); if (credential instanceof PublicKeyCredential) { String responseJson = ((PublicKeyCredential) credential) .getAuthenticationResponseJson(); fidoAuthenticateToServer(responseJson); } else if (credential instanceof PasswordCredential) { Log.d(TAG, "Got PasswordCredential"); String id = ((PasswordCredential) credential).getId(); String password = ((PasswordCredential) credential) .getPassword(); firebaseSignInWithPassword(id, password); } else { Log.e( TAG, "Unexpected type of credential: " + credential.getClass().getName()); } } @Override public void onError(GetCredentialException e) { Log.e(TAG, "Sign in failed with exception", e); } } );
The following snippet shows an example of how to format the JSON request when you get a passkey:
{
"challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
"allowCredentials": [],
"timeout": 1800000,
"userVerification": "required",
"rpId": "credential-manager-app-test.glitch.me"
}
The following code snippet shows an example JSON response after you get a public key credential:
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
"signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
"userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
}
}
Registration flows
You can register a user for authentication using either a passkey or a password.
Create a passkey
To give users the choice to enroll a passkey and use it for reauthentication,
register a user credential using a CreatePublicKeyCredentialRequest
object:
Kotlin
fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) { val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest( // Contains the request in JSON format. Uses the standard WebAuthn // web JSON spec. requestJson = requestJson, // Defines whether you prefer to use only immediately available credentials, // not hybrid credentials, to fulfill this request. This value is false // by default. preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials, ) // Execute CreateCredentialRequest asynchronously to register credentials // for a user account. Handle success and failure cases with the result and // exceptions, respectively. coroutineScope.launch { try { val result = credentialManager.createCredential( request = createPublicKeyCredentialRequest, activity = activity, ) handlePasskeyRegistrationResult(result) } catch (e : CreateCredentialException){ handleFailure(e) } } fun handleFailure(e: CreateCredentialException) { when (e) { is CreatePublicKeyCredentialDomException -> { // Handle the passkey DOM errors thrown according to the // WebAuthn spec. handlePasskeyError(e.domError) } is CreateCredentialCancellationException -> { // The user intentionally canceled the operation and chose not // to register the credential. } is CreateCredentialInterruptedException -> { // Retry-able error. Consider retrying the call. } is CreateCredentialProviderConfigurationException -> { // Your app is missing the provider configuration dependency. // Most likely, you're missing the // "credentials-play-services-auth" module. } is CreateCredentialUnknownException -> ... is CreateCustomCredentialException -> { // You have encountered an error from a 3rd-party SDK. If you // make the API call with a request object that's a subclass of // CreateCustomCredentialRequest using a 3rd-party SDK, then you // should check for any custom exception type constants within // that SDK to match with e.type. Otherwise, drop or log the // exception. } else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}") } } }
Java
public void createPasskey(String requestJson, boolean preferImmediatelyAvailableCredentials) { CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest = // `requestJson` contains the request in JSON format. Uses the standard // WebAuthn web JSON spec. // `preferImmediatelyAvailableCredentials` defines whether you prefer // to only use immediately available credentials, not hybrid credentials, // to fulfill this request. This value is false by default. new CreatePublicKeyCredentialRequest( requestJson, preferImmediatelyAvailableCredentials); // Execute CreateCredentialRequest asynchronously to register credentials // for a user account. Handle success and failure cases with the result and // exceptions, respectively. credentialManager.createCredentialAsync( createPublicKeyCredentialRequest, requireActivity(), cancellationSignal, requireContext().getMainExecutor(), new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> { @Override public void onResult(CreateCredentialResponse result) { handleSuccessfulCreatePasskeyResult(result); } @Override public void onError(CreateCredentialException e) { if (e instanceof CreatePublicKeyCredentialDomException) { // Handle the passkey DOM errors thrown according to the // WebAuthn spec. handlePasskeyError(((CreatePublicKeyCredentialDomException)e).getDomError()); } else if (e instanceof CreateCredentialCancellationException) { // The user intentionally canceled the operation and chose not // to register the credential. } else if (e instanceof CreateCredentialInterruptedException) { // Retry-able error. Consider retrying the call. } else if (e instanceof CreateCredentialProviderConfigurationException) { // Your app is missing the provider configuration dependency. // Most likely, you're missing the // "credentials-play-services-auth" module. } else if (e instanceof CreateCredentialUnknownException) { } else if (e instanceof CreateCustomCredentialException) { // You have encountered an error from a 3rd-party SDK. If // you make the API call with a request object that's a // subclass of // CreateCustomCredentialRequest using a 3rd-party SDK, // then you should check for any custom exception type // constants within that SDK to match with e.type. // Otherwise, drop or log the exception. } else { Log.w(TAG, "Unexpected exception type " + e.getClass().getName()); } } } ); }
Format the JSON request
After you create a passkey, you must associate it with a user's account, and store the passkey's public key on your server. The following snippet shows an example of how to format the JSON request when you create a passkey.
This blog post about bringing seamless authentication to your apps shows you how to format your JSON request when you create passkeys and when you authenticate using passkeys. It also explains why passwords aren't an effective authentication solution, how to leverage existing biometric credentials, how to associate your app with a website that you own, how to create passkeys, and how to authenticate using passkeys.
{
"challenge": "nhkQXfE59Jb97VyyNJkvDiXucMEvltduvcrDmGrODHY",
"rp": {
"name": "CredMan App Test",
"id": "credential-manager-app-test.glitch.me"
},
"user": {
"id": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0",
"name": "helloandroid@gmail.com",
"displayName": "helloandroid@gmail.com"
},
"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"
}
}
Handle the JSON response
The following code snippet shows an example JSON response for creating a public key credential. Learn more about how to handle the returned public key credential.
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
}
}
Save a user's password
If the user provides a username and password for an authentication flow in your
app, you can register a user credential that can be used to authenticate the
user. To do so, create a CreatePasswordRequest
object:
Kotlin
fun registerPassword(username: String, password: String) { // Initialize a CreatePasswordRequest object. val createPasswordRequest = CreatePasswordRequest(id = username, password = password) // Create credentials and handle result. coroutineScope.launch { try { val result = credentialManager.createCredential(createPasswordRequest) handleRegisterPasswordResult(result) } catch (e: CreateCredentialException) { handleFailure(e) } } }
Java
void registerPassword(String username, String password) { // Initialize a CreatePasswordRequest object. CreatePasswordRequest createPasswordRequest = new CreatePasswordRequest(username, password); // Register the username and password. credentialManager.createCredentialAsync( createPasswordRequest, requireActivity(), cancellationSignal, requireContext().getMainExecutor(), new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> { @Override public void onResult(CreateCredentialResponse result) { handleResult(result); } @Override public void onError(CreateCredentialException e) { handleFailure(e) } } ); }
Support credential recovery
If a user no longer has access to a device where they had stored their credentials, they might need to recover from a secure online backup. To learn more about how to support this credential recovery process, read the section titled "Recovering access or adding new devices" in this blog post: Security of Passkeys in the Google Password Manager.
Troubleshoot common errors
The following table shows several common error codes and descriptions, and provides some information about their causes:
Error code and description | Cause |
---|---|
On Begin Sign In Failure: 16: Caller has been temporarily blocked due to too many canceled sign-in prompts. | If you encounter this 24-hour cooldown period during development, you can reset it by clearing Google Play services' app storage. Alternatively, to toggle this cooldown on a test device or emulator, go
to the Dialer app and input the following code:
|
On Begin Sign In Failure: 8: Unknown internal error. |
|
CreatePublicKeyCredentialDomException: The incoming request cannot be validated | The app's package ID is not registered with your server. Validate this in your server-side integration. |
Additional resources
To learn more about the Credential Manager API and about passkeys, view the following developer guides:
- Video: How to reduce reliance on passwords in Android apps with passkey support
- Codelab: Learn how to simplify auth journeys using Credential Manager API in your Android app
- Sample app: CredentialManager