Passkey Mobile Integration Guide
This document provides step-by-step instructions for integrating passkey registration and authentication in your native mobile app using IDaaS.
Overview
Native mobile passkey integration empowers users to log in securely using biometrics or device credentials—like fingerprint, facial recognition, or PIN—without the need for passwords. Passkeys are stored locally in secure platform-specific storage (such as Android Keystore or iOS Secure Enclave) and are uniquely bound to your app, ensuring both high security and a smooth, app-native authentication experience.
OS Version Support
iOS Support
Passkey support is natively available on iOS 16 and later. Apple introduced passkeys as part of its broader push toward passwordless authentication using the WebAuthn standard. On supported devices, users can authenticate using Face ID, Touch ID, or device passcode, with passkeys securely synced via iCloud Keychain.
- ✅ Minimum supported version:
iOS 16 - 🔐 Biometric and device-based authentication
- ☁️ Syncs across Apple devices via iCloud
Android Support
Android devices support passkeys starting from Android 9 (Pie) via browser-based WebAuthn flows. However, native passkey support using the Credential Manager API is available from Android 14 onward. This enables seamless biometric authentication and passkey storage via Google Password Manager.
- ✅ Minimum supported version:
Android 9(browser-based) - 🌟 Full native support:
Android 14+ - 🔐 Uses biometric or screen lock authentication
- ☁️ Syncs via Google Password Manager
Association Files
Passkeys require association files to establish trust between your app and the domain. These files are essential for verifying the authenticity of the passkeys and ensuring secure communication between the app and your domain.
Association files must be hosted on your domain to enable passkey functionality. They are used to verify the association between your app and the domain, allowing users to register and authenticate using passkeys seamlessly.
Association files are typically located at the following paths:
- For Android:
https://yourdomain.com/.well-known/assetlinks.json - For iOS:
https://yourdomain.com/.well-known/apple-app-site-association
For native mobile applications, proper relying party configuration requires additional setup to establish trust between your app and your domain:
Android Configuration
Find the official documentation for Android passkey integration at Android Credential Manager.
1. Create and host assetlinks.json
Create an assetlinks.json file and host it at https://yourdomain.com/.well-known/assetlinks.json:
[
{
"relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.yourapp",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
]
}
}
]
Key Fields:
package_name: Your Android app's package identifier.sha256_cert_fingerprints: SHA256 fingerprint of your app's signing certificate.
2. Obtain Certificate Fingerprint
keytool -list -v -keystore your-release-key.keystore -alias your-key-alias
3. App Manifest Configuration
Add intent filters to your Android manifest:
<activity android:name=".MainActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="yourdomain.com" />
</intent-filter>
</activity>
iOS Configuration
Find the official documentation for iOS passkey integration at Apple Passkeys
1. Create and host apple-app-site-association
Create an apple-app-site-association file and host it at
https://yourdomain.com/.well-known/apple-app-site-association:
{
"webcredentials": {
"apps": ["TEAMID.com.yourcompany.yourapp"]
}
}
Key Fields:
TEAMID: Your Apple Developer Team ID.com.yourcompany.yourapp: Your iOS app's bundle identifier.webcredentials: Essential for passkey functionality
For more information - https://developer.apple.com/documentation/xcode/supporting-associated-domains
2. App Configuration
Add associated domains to your iOS app entitlements:
<plist>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:yourdomain.com</string>
<string>webcredentials:yourdomain.com</string>
</array>
</plist>
Prerequisites
- IDaaS Account: Ensure you have an IDaaS account with access to the Administration API.
- Native App: Your mobile app must be registered in IDaaS with the correct package name/bundle identifier (see instructions below).
- Web Domain: Your web domain must be configured to host the association files.
- SSL Certificate: Ensure your domain has a valid SSL certificate to serve the association files over HTTPS.
Create Administration API Application
- Log in to your IDaaS account
- Navigate to Applications and create a new Administration API application
- Provide the required details:
- Application name
- Description
- Required permissions for FIDO token management
- Save the application ID and shared secret for your backend integration
Configure Relying Party Settings
- Navigate to the Passkey/FIDO2 settings page of your IDaaS account
- Enable the Enable Passkeys from Custom Web or Native Apps option and click the Add button
- Enter the Relying Party ID (RP ID) of your domain name (e.g.,
yourdomain.com) - Click the Sync with Association Files button to automatically fetch the origin settings from your hosted association files
If you have not hosted the association files yet, you can manually enter your origin settings.
- Click OK to close the dialog, then click Save to apply the changes.
Create Authentication API Application
- Navigate to Applications and create a new Authentication API application
- Provide the required details:
- Application name
- Description
- Save the application ID for your frontend integration
Configure Resource Rules
- Navigate to the Resource Rules page
- Create a new resource rule for your Authentication API application
- Select Passkey/FIDO2 as one of the authentication methods
- Ensure Passkey is enabled in the Login Flow settings
Integration Steps
Backend Setup
See the Getting Started page for help with initializing the IDaaS SDK in your backend application.
Your backend will handle communication with the IDaaS APIs, while your frontend manages native mobile WebAuthn API interactions.
Communication between your mobile app and your backend application is out of scope for this document. The typical flow involves the mobile app making API calls to your backend, which in turn interacts with IDaaS for passkey registration and authentication.
Passkey Registration
1. Start Passkey Registration
Initiate the passkey registration process by creating a FIDO token for the user. This involves generating a challenge and providing the necessary authenticator settings.
GET /api/web/v1/fidotokens/challenge/:id
Send the registration parameters to your mobile app.
Refer to the code snippet below for an example. The examples below demonstrate usage within the context of the IDaaS server. Modify as needed to align with your own API server setup.
- Android
- iOS
// To initiate passkey registration, first obtain the challenge from the API server.
// The userUuid below is the unique id of the user in IDaaS. Obtain it from IDaaS.
val response = apiClient.startRegister(userUuid)
// extract the challenge object from the response.
val fidoRegisterChallenge = response.body()
// When your mobile app receives the challenge, pass the parameters into the mobile WebAuthn API and it should prompt the user to register their passkey.
// The user will interact with the device's biometric or security features to complete the registration.
// create passkey
val credentialJson = createPasskey(
context, prepareCreatePasskeyJsonRequest(
context, username, fidoRegisterChallenge)
)
If you don't have the UUID of the user you want to register a passkey for, you can get it by fetching the user details via the POST /api/web/v3/users/userid API.
The code snippet below shows you how to format your JSON request when you create passkeys as per WebAuthn standards. For reference, read Format the JSON request
fun prepareCreatePasskeyJsonRequest(
context: Context,
username: String,
fido: FIDORegisterChallenge): String {
return """
{
"challenge": "${fido.challenge}",
"rp": {
"name": "Relying Party Id Name",
"id": "example.com"
},
"user": {
"id": "${fido.userid}",
"name": "${username}",
"displayName": "${fido.userDisplayName}"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": "${fido.timeoutMillis}",
"attestation": "none",
"excludeCredentials": [
{
"id": "ghi789",
"type": "public-key"
},
{
"id": "jkl012",
"type": "public-key"
}
],
"authenticatorSelection": {
"authenticatorAttachment": "${fido.registrationAuthenticatorAttachment.name.toLowerCase()}",
"residentKey": "${fido.registrationRequireResidentKey.name.toLowerCase()}",
"userVerification": "${fido.registrationUserVerification.name.toLowerCase()}"
}
}
""".trimIndent()
}
Register a user credential using a CreatePublicKeyCredentialRequest object.
suspend fun createPasskey(
context: Context, requestJson: String, preferImmediatelyAvailableCredentials: Boolean = false
): String? {
// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)
val createRequest = CreatePublicKeyCredentialRequest(
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
)
return try {
val result = credentialManager.createCredential(context, createRequest)
(result as? CreatePublicKeyCredentialResponse)?.registrationResponseJson
// complete passkey registration by calling
} catch (e: CreateCredentialException) {
Toast.makeText(context, "Error registering passkey!!", Toast.LENGTH_LONG).show()
null
}
}
do {
// To initiate passkey registration, first obtain the challenge from the API server.
// The userUuid below is the unique id of the user in IDaaS. Obtain it from IDaaS.
let fidoRegistrationChallenge =
try await client.startFidoTokenRegistration(userUuid: userUuid)
// extract the challenge from the fidoRegistrationChallenge.
let challenge = Data(
base64Encoded: fidoRegistrationChallenge.challenge
.base64UrlToBase64
)!
let userId = Data(
base64Encoded: fidoRegistrationChallenge.userId
.base64UrlToBase64
)!
let name = fidoRegistrationChallenge.userDisplayName
let publicKeyCredentialProvider =
ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: "example.com")
// create passkey
let registrationRequest = publicKeyCredentialProvider
.createCredentialRegistrationRequest(
challenge: challenge,
name: name,
userID: userId )
let authController = ASAuthorizationController(
authorizationRequests: [registrationRequest])
authController.delegate = self
authController.presentationContextProvider = self
authController.performRequests()
} catch {
print (
"Something went wrong registering passkey for user: \(self.username), \(error)"
)
}
If you don't have the UUID of the user you want to register a passkey for, you can get it by fetching the user details via the POST /api/web/v3/users/userid API.
The user will interact with the device's biometric or security features to complete the registration.
2. Complete Passkey Registration
After the user successfully registers their passkey, your mobile app will receive an attestation response. This response contains the attestation object and client data JSON, which must be sent to your backend server to complete the registration process with IDaaS.
POST /api/web/v1/fidotokens/complete/:id
The request body includes a 'name' parameter representing the passkey name, which must be unique for each passkey associated with the user.
- Android
- iOS
The below code snippet sends the created credential above to IDaaS API Server to complete the registration
// call API Server to register passkey on server.
val response = apiClient.completeFIDORegister(
prepareCompleteRegisterRequest(userUUID.toString(), credentialJson))
if (!(response?.isSuccessful ?: false)) {
Toast.makeText(context, "Error registering passkey!!", Toast.LENGTH_LONG).show()
}
The code snippet below prepares the payload to be submitted to the IDaaS API server for passkey registration.
private fun prepareCompleteRegisterRequest( userUUID: String,
credentialJson: String? ): CompleteFidoRegisterRequest {
val jsonResult = JSONObject(credentialJson);
val clientDataJSON = jsonResult.getJSONObject("response").getString("clientDataJSON")
val attestationObject = jsonResult.getJSONObject("response").getString("attestationObject")
val completeRegistrationRequest = CompleteFidoRegisterRequest(
userUUID, "Unique-Passkey-Name", clientDataJSON, attestationObject)
return completeRegistrationRequest
}
The below code snippet sends the created credential above to IDaaS API Server to complete the registration
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
switch authorization.credential {
case let credentialRegistration
as ASAuthorizationPlatformPublicKeyCredentialRegistration:
// Verify the attestationObject and clientDataJSON with your service.
// The attestationObject contains the user's new public key to store and use for subsequent sign-ins.
let attestationObject = credentialRegistration.rawAttestationObject
let clientDataJSON = credentialRegistration.rawClientDataJSON
Task {
do {
// FidoAttestationRequest object below represents the request payload structure.
// Modify it as per your Server API requirement.
let payload = FidoAttestationRequest(
attestationObject:
attestationObject.base64EncodedString(),
clientDataJSON: clientDataJSON.base64EncodedString(),
name: "Unique-Passkey-Name",
)
// call IDaaS API server to complete the passkey registration.
let completeFidoRegistrationResponse =
try await client.completeFidoTokenRegistration(
userUuid: userUuid,
attestation: payload
)
print (
"A new passkey is registered in IDaaS. Complete passkey registration response: \(completeFidoRegistrationResponse)"
)
} catch {
print (
"Something went wrong completing passkey registration in IDaaS. Error: \(error)"
)
}
}
}
}
Once registered successfully, the user can view the Passkey/FIDO2 credential listed as one of the authenticators in their IDaaS profile.
Passkey Authentication
1. Start Passkey Authentication
To authenticate users with passkeys, your mobile app must initiate the authentication flow by sending a request to your backend server which will request an authentication challenge from IDaaS.
POST /api/web/v2/authentication/users/authenticate/PASSKEY
Request Body:
{
"applicationId": "Authentication API application ID",
"userId": "Optional. User ID of the authenticating user",
"rpId": "The Relying Party ID of the registered passkey"
}
Your backend should then return the challenge to your mobile app and into the mobile WebAuthn API. The user will interact with the device's biometric or security features to complete the authentication.
The userId is optional. If not provided, the user will be prompted to select their passkey from a list of available
passkeys on the device. If provided, the user will be authenticated using the specified passkey.
Note that the challenge request API will return a token that must be used in the subsequent authentication completion
request.
- Android
- iOS
try {
// Initiate challenge for passkey authentication.
// userChallengeParameters is the object payload to fetch challenge from IDaaS.
val response = apiClient.userChallenge(userChallengeParameters)
val authenticatedResponse = response?.body()
val credentialManager = CredentialManager.create(context)
// Get passkey from the user's public key credential provider.
val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
requestJson = getPasskeyRequestJson(context, authenticatedResponse))
// Retrieves the user's saved password for your app from their password provider.
val getPasswordOption = GetPasswordOption()
val getCredentialRequest = GetCredentialRequest(
listOf(getPublicKeyCredentialOption, getPasswordOption)
)
val result = credentialManager.getCredential(
// Use an activity-based context to avoid undefined system UI launching behavior.
context = context, request = getCredentialRequest
)
// call IDaaS server API to validate the credentials.
// Refer code example in Complete Passkey Authentication section below.
handleSignIn(result, authenticatedResponse?.token.toString(), context)
} catch (e: GetCredentialException) {
Log.e("CredentialManager", "No credential available", e)
Toast.makeText(context, "Error signing in passkey!!", Toast.LENGTH_LONG).show()
// handle exception here
}
The following example shows how to format the JSON request when you get a passkey.
fun getPasskeyRequestJson(context: Context,
challengeResponse: AuthenticatedResponse?): String {
return """
{
"challenge": "${challengeResponse?.fidoChallenge?.challenge}",
"timeout": "${challengeResponse?.fidoChallenge?.timeoutMillis}",
"userVerification": "required",
"rpId": "example.com"
}
""".trimIndent()
}
do {
// Initiate challenge for passkey authentication.
// userChallengeParameters is the object payload to fetch challenge from IDaaS.
let fidoRegistrationChallenge =
try await client.requestUserChallenge(authenticator: userChallengeParameters)
let authToken = fidoRegistrationChallenge.token
let challenge = (fidoRegistrationChallenge.fidoChallenge?.challenge)!
let publicKeyCredentialProvider =
ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: "example.com")
let assertionRequest =
publicKeyCredentialProvider.createCredentialAssertionRequest(
challenge: Data(
base64Encoded: challenge.base64UrlToBase64)!
)
let authController = ASAuthorizationController(
authorizationRequests: [assertionRequest])
authController.delegate = self
authController.presentationContextProvider = self
authController.performRequests()
} catch {
print (
"Something went wrong authenticating passkey for user: \(self.username), \(error)"
)
}
2. Complete Passkey Authentication Response
After the user successfully authenticates, your mobile app will receive an assertion response. This response contains the authenticator data, client data JSON, credential ID, and signature, which must be sent to your backend server to complete the authentication process with IDaaS.
POST /api/web/v1/authentication/users/authenticate/PASSKEY/complete
Make sure you include the Authorization header with the token received from the challenge request.
Below is a sample implementation that forwards the generated credential to the IDaaS API server for validation.
- Android
- iOS
suspend fun handleSignIn(response: GetCredentialResponse,
authToken: String, context: Context) {
if (response.credential is PublicKeyCredential) {
val userAuthenticateParams = UserAuthenticateParameters().applicationId(AUTH_APPLICATION_ID)
.fidoResponse(buildFidoResponse(response))
// call idaas server to authenticate
val response = apiClient.authenticate(userAuthenticateParams, authToken)
if (!(response?.isSuccessful ?: false)) {
Toast.makeText(context, "Error signing in!!", Toast.LENGTH_LONG).show()
}
}
}
fun buildFidoResponse(credentialResponse: GetCredentialResponse): FIDOResponse {
val cred = credentialResponse.credential as PublicKeyCredential
val responseJson = cred.authenticationResponseJson
val jsonObject = JSONObject(responseJson)
val responseJsonObj = jsonObject.getJSONObject("response")
val authenticatorData = responseJsonObj.getString("authenticatorData")
val clientData = responseJsonObj.getString("clientDataJSON")
val signature = responseJsonObj.getString("signature")
val userHandle = responseJsonObj.getString("userHandle")
val fidoResponse = FIDOResponse().userHandle(userHandle).clientDataJSON(clientData)
.authenticatorData(authenticatorData).signature(signature)
return fidoResponse
}
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
switch authorization.credential {
case let credentialAssertion
as ASAuthorizationPlatformPublicKeyCredentialAssertion:
// Verify the below signature and clientDataJSON with IDaaS for the given userID.
Task {
do {
let clientDataJSON = credentialAssertion.rawClientDataJSON
let credentialID = credentialAssertion.credentialID
let signature = credentialAssertion.signature
let userID = credentialAssertion.userID
let authenticatorData = credentialAssertion.rawAuthenticatorData
let fidoResponsePayload = FidoResponse(
authenticatorData:
authenticatorData.base64EncodedString(),
clientDataJSON: clientDataJSON.base64EncodedString(),
credentialId: credentialID.base64EncodedString(),
signature: signature.base64EncodedString(),
userHandle: userID.base64EncodedString()
)
let response = try await client.authenticateUserChallenge(
authenticator: "PASSKEY",
body: AuthenticationRequest(
// AUTH_APPLICATION_ID to be referred from IDaaS.
applicationId: AUTH_APPLICATION_ID,
fidoResponse: fidoResponsePayload
)
)
print (
"Passkey authentication in IDaaS successful. Authentication result: \(response)"
)
} catch {
print (
"Something went wrong completing passkey authentication in IDaaS. Error: \(error)"
)
}
}
}
}