Improving the Login Experience and Reducing Cost: Passkey Integration in Android Apps
In today's fast-paced digital landscape, where convenience and security are paramount, finding the right balance between user experience and robust authentication measures is essential for organisations. At Halodoc, we understand the significance of improving the Login experience while maintaining stringent security standards.
In this blog post, we explore our latest endeavour the integration of Passkey authentication into our Android apps. This innovative solution not only enhances user experience by improving the Login experience but also reduces costs associated with traditional OTP based authentication methods. Join us as we delve into the implementation of Passkey integration and its transformative impact on our Android app ecosystem.
Passkey Implementation
Let's now delve into the implementation details of Passkey within our Android application. The foundational implementation can be categorised into below key segments:
- Digital Asset Link Configuration
- Authenticator Integration
- ProGuard Configuration
- Passkey Registration
- Passkey Authentication
Let's explore each section in depth!
1. Digital Asset Link Configuration
To initiate support for passkeys within our Android application, the first step is to establish an association between our app and the corresponding website. This involves declaring associations via a Digital Asset Links JSON file hosted on our website and integrating a link to this file within our app's manifest.
The Digital Asset Links protocol and API enable apps and websites to assert verifiable statements about each other. This allows for associations between a website and a specific Android app, enhancing interoperability for seamless authentication experiences. This streamlined approach promotes trust and facilitates secure interactions across platforms.
[
{
"relation" : [
"delegate_permission/common.handle_all_urls",
"delegate_permission/common.get_login_creds"
],
"target" : {
"namespace" : "android_app",
"package_name" : "com.example.android",
"sha256_cert_fingerprints" : [
SHA_HEX_VALUE
]
}
}
]
Relation Field: The relation
field specifies the relationship between the website and the app. Here, we declare two relations:
- delegate_permission/common.handle_all_urls: Grants the app the ability to handle all URLs, facilitating smooth app-linking functionality.
delegate_permission/common.get_login_creds: Specifies the app's capability to retrieve sign-in credentials, essential for Passkey Credential Manager Authenticator.
Target Field: The target
field specifies the asset the declaration applies to, including:
- namespace: Identifies the asset as belonging to an Android app.
- package_name: Specifies the package name declared in the app's manifest.
- SHA_HEX_VALUE: Lists the SHA256 fingerprints of our app, ensuring secure verification.
Hosting Digital Asset Link File: To enable proper verification by Google, we host the Digital Assets Link JSON file at the following public location on our sign-in domain:
https://[domain_name_of_website]/.well-known/assetlinks.json
By configuring Digital Asset Links in this manner, we establish a trusted connection between our website and app, facilitating secure authentication processes for our users.
2. Authenticator Integration
To integrate credential management authenticator functionality into our app, add the specified dependencies to our app module's build script.
implementation("androidx.credentials:credentials:1.3.0-alpha03")
This provides the core credential management features.
implementation("androidx.credentials:credentials-play-services-auth:1.3.0-alpha03")
This provides for devices running Android 13 and below to support credentials via Google Play Services.
Passkeys are exclusively supported on devices running Android 9 (API level 28) or higher
3. ProGuard Configuration
The ProGuard directives aim to preserve specific classes within the Android application, ensuring they are not obfuscated during the code shrinking and optimisation process.
-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** { *; }
specifies that the class androidx.credentials.CredentialManager
should be preserved from obfuscation.
-keep class androidx.credentials.playservices.** { *; }
ensures that all classes within the package androidx.credentials.playservices
and its sub-packages are retained without obfuscation. This preservation is essential for maintaining the functionality and integrity of classes related to Google Play Services authentication within the application.
4. Passkey Registration
To utilise the Credential Manager for passkey registration, it is imperative to initialise the instance with either the application context or activity context. The following code demonstrates how to create a Credential Manager instance using the context:
val credentialManager = CredentialManager.create(context)
To initiate the Passkey registration process, users are required to create a Passkey and associate it with their account. Subsequently, the Passkey's public key is stored on the backend server, while the private key is securely stored on the user's device.
Let's delve deeper into the registration process:
- When a user attempts to create a passkey, the client will call our server to request a challenge and other necessary metadata required for passkey creation by the Credential Manager Authenticator.
{
"challenge": "abc123",
"rp": {
"name": "Credential Manager example",
"id": "credential-manager-test.example.com"
},
"user": {
"id": "def456",
"name": "helloandroid@gmail.com",
"displayName": "helloandroid@gmail.com"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": 1800000,
"attestation": "none",
"excludeCredentials": [
{"id": "ghi789", "type": "public-key"},
{"id": "jkl012", "type": "public-key"}
],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
- The client utilizes this response to prompt the authenticator to generate a new key pair. Before generating the key pair, the authenticator verifies the RpId in the response with the domain name of the Digital Asset link for security purposes. After verification, the newly created key pair serves to verify the user's identity to the relying party.

- Passkey Creation Process: The
createPasskey
function initialises passkey creation by sending a backend JSON response to theCreateCredentialRequest
, enabling asynchronous registration of user credentials. - User Consent and Credential Storage: Upon invocation, the function patiently awaits user consent for passkey usage, which involve biometric fingerprint or device PIN authentication. It also specifies the credential provider and account for passkey storage, thereby ensuring a secure authentication process.
- The credential provider generates a fresh asymmetric key pair, preserving the private key securely within the device while transmitting the public key and pertinent details encapsulated in the JSON structure below to the client.
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
}
}
- Subsequently, the client forwards the JSON containing the public key to our backend for validation and storing purposes, crucial for facilitating the user's login procedure.
5. Passkey Authentication
When a user attempts to log in by selecting the passkey option from the prompt, the authenticator will furnish a signature generated by the private key linked with the credential. On the backend server, the validity of this signature for login purposes will be verified using the corresponding public key.
Let's delve deeper into the authentication process:
- When users opt for Passkey authentication, the client application will send a request to our server, fetching a challenge and crucial metadata essential for the Credential Manager Authenticator to proceed with the login process.
{
"challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
"allowCredentials": [],
"timeout": 1800000,
"userVerification": "required",
"rpId": "credential-manager-app-test.glitch.me"
}
- The client requires the authenticator to use one of the passkeys linked to the relying party to sign this JSON.

- Requesting Passkey Credentials: The
loginWithPasskey
function retrieves Passkey credentials by sending JSON data from the backend to the authenticator. The authenticator verifies the RpId in the response against the domain name in the Digital Asset link for security purposes. It then locates a suitable credential corresponding to the Relying Party ID and initiates the authentication process. - User Consent and Authentication: Upon identification of a matching credential, the credential provider prompts the user to provide consent for authentication. Triggering the creation of a new assertion by signing over the clientDataHash and authenticatorData using the private key associated with the user's account.
{
"id": "DEastxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "DEastxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eDEaseXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8tDEasQdAAAAAA",
"signature": "MEUCIQCO1Cm4SA2xDEasdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
"userHandle": "2HzoHm_hDEasuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
}
}
- Data Transfer to Backend Server: The authenticator returns the authenticatorData and assertion signature to the client, which then forwards this data to the backend Relying Party server.
- Signature Validation: Here, the signature undergoes validation against the public key, ultimately determining the authentication result. If the validation is successful, the user gains access to our app's services.
Conclusion
The introduction of Passkey authentication marks a significant leap in enhancing user experience and security in our Android applications. With Passkey authentication, powered by biometric verification, we ensure robust account protection against potential threats while simplifying the login process and reducing operational costs associated with traditional OTP methods.
Moving forward, our commitment to innovation drives us to continually refine Passkey authentication and explore new solutions. With Passkey authentication, users can confidently access our Android apps, knowing their accounts are safeguarded by advanced security measures, while enjoying a seamless login experience.
References





Join us
Scalability, reliability and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and if solving hard problems with challenging requirements is your forte, please reach out to us with your resume at careers.india@halodoc.com.
About Halodoc
Halodoc is the number 1 Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek, and many more. We recently closed our Series D round and in total have raised around USD$100+ million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalized for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.