> For AI agents: [llms.txt](https://docs.trustedauth.com/llms.txt) provides a Markdown index of this documentation.

# Implement passkeys in native mobile applications

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 lets users sign in securely using biometrics or device credentials (such as fingerprint, facial recognition, or PIN) without passwords. Passkeys are stored locally in platform-specific secure storage (Android Keystore or iOS Secure Enclave) and are bound to your app, providing strong security and a smooth, 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 with **iCloud Keychain**.

- ✅ Minimum supported version: `iOS 16`
- 🔐 Biometric and device-based authentication
- ☁️ Syncs across Apple devices through iCloud

### Android support

Android devices support passkeys starting from **Android 9 (Pie)** using 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 with **Google Password
Manager**.

- ✅ Minimum supported version: `Android 9` (browser-based)
- 🌟 Full native support: `Android 14+`
- 🔐 Uses biometric or screen lock authentication
- ☁️ Syncs through 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.

For your default IDaaS tenant domain, association files are hosted automatically.
However, if you configure a custom domain, origin, or relying party ID, you must
host the association files on that domain yourself 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](https://developer.android.com/identity/sign-in/credential-manager).

#### 1. Create and host `assetlinks.json`

Create an `assetlinks.json` file and host it at `https://yourdomain.com/.well-known/assetlinks.json`:

```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

```bash
keytool -list -v -keystore your-release-key.keystore -alias your-key-alias
```

#### 3. App manifest configuration

Add intent filters to your Android manifest:

```xml
<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](https://developer.apple.com/documentation/authenticationservices/supporting-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`:

```json
{
  "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:

```xml
<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

1. Log in to your IDaaS account
2. Navigate to **Applications** and create a new
   **Administration API** application.
3. Provide the required details:
   - Application name
   - Description
   - Required permissions for FIDO token management
4. Save the **application ID** and **shared secret** for your backend
   integration.

### Configure Relying Party settings

1. Navigate to the **Passkey/FIDO2** settings page of your IDaaS account
2. Enable the **Enable Passkeys from Custom Web or Native Apps** option and
   click the **Add** button
3. Enter the **Relying Party ID** (RP ID) of your domain name (for example,
   `yourdomain.com`)
4. Click **Sync with Association Files** to automatically fetch the
   origin settings from your hosted association files

info

If you have not hosted the association files yet, you can manually enter your
origin settings.

5. Click **OK** to close the dialog.
6. Click **Save** to apply the changes.

### Create Authentication API application

1. Navigate to Applications and create a new
   Authentication API application
2. Provide the required details:
   - Application name
   - Description
3. Save the **application ID** for your frontend integration

### Configure resource rules

Configure a resource rule to allow Passkey/FIDO2 authentication for your Authentication API application.

1. Navigate to the **Resource Rules** page.
2. Create a new resource rule for your Authentication API application.
3. Select **Passkey/FIDO2** as one of the authentication methods.
4. Ensure **Passkey** is enabled in the Login Flow settings.

For help, see [Create and manage resource rules](/docs/authentication-and-security/create-and-manage-resource-rules/create-resource-rules.md).

## Integration steps

Complete the following steps to implement passkey registration and authentication in your native mobile app:

- [Step 1: Backend setup](#step-1-backend-setup)
- [Step 2: Passkey registration](#step-2-passkey-registration)
- [Step 3: Passkey authentication](#step-3-passkey-authentication)

### Step 1: Backend setup

See the [Getting Started](/developer.md) page for help with
initializing the IDaaS SDK in your backend application.

Your backend handles 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.

### Step 2: 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](/api/openapi/admin-api/start-create-fido-token-using-get.md)

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**

```kotlin
// 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 interacts with the device's biometric or security features to complete the registration.

// create passkey
val credentialJson = createPasskey(
context, prepareCreatePasskeyJsonRequest(
context, username, fidoRegisterChallenge)
)
```

tip

If you do not have the user's UUID, you can retrieve it by fetching the user details
through the [POST /api/web/v3/users/userid](/api/openapi/admin-api/user-by-userid-using-post.md) API.

The code snippet below shows you how to format your JSON request when you create passkeys according to WebAuthn
standards. For reference, read [Format the JSON request](https://developer.android.com/identity/sign-in/credential-manager#format-json-request)

```kotlin
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": "${fido.attestation.name.lowerCase()}",
            "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.

```kotlin
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
    }
}
```

**iOS**

```swift
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)"
    )
}
```

tip

If you do not have the user's UUID, you can retrieve it by fetching the user details
using the [POST /api/web/v3/users/userid](/api/openapi/admin-api/user-by-userid-using-post.md) API.

The user interacts 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
receives 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](/api/openapi/admin-api/complete-create-fido-token-using-post.md)

warning

The request body includes a `name` parameter representing the passkey name,
which must be unique for each passkey associated with the user.

**Android**

The below code snippet sends the created credential above to IDaaS API Server to complete the registration

```kotlin
// 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.

```kotlin
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
}
```

**iOS**

The below code snippet sends the created credential above to IDaaS API Server to complete the registration

```swift
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 to match your server API requirements.
                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.

### Step 3: 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](/api/openapi/auth-api/user-challenge-using-post.md) Request Body:

```json
{
  "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 interacts with the device's biometric or
security features to complete the authentication.

The `userId` is optional. If not provided, the user is prompted to select
their passkey from a list of available passkeys on the device. If provided, the
user is authenticated using the specified passkey.

The challenge request API returns a `token` that must be used in
the subsequent authentication completion request.

**Android**

```kotlin
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.

```kotlin
fun getPasskeyRequestJson(context: Context,
        challengeResponse: AuthenticatedResponse?): String {
    return """
    {
        "challenge": "${challengeResponse?.fidoChallenge?.challenge}",
        "timeout": "${challengeResponse?.fidoChallenge?.timeoutMillis}",
        "userVerification": "required",
        "rpId": "example.com"
    }
    """.trimIndent()
}
```

**iOS**

```swift
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 receives 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](/api/openapi/auth-api/user-authenticate-using-post.md)

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**

```kotlin
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()
        }
    }
}
```

```kotlin
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
}
```

**iOS**

```swift
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)"
                )
            }
        }
    }
}
```
