> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Passkeys

Passwordless authentication with biometrics or device credentials.

## Prerequisites

* SDK initialized
* Domain you control (for `.well-known` files)
* App package name and SHA-256 certificate fingerprint
* Android SDK 28+

## App Configuration

There are a few steps to configure passkey support. If you have already set up an associated domain for your application, you can skip to [Managing Passkeys](#managing-passkeys).

### Set Up an Associated Domain

On your webserver, set up this route:

```
GET https://{{yourdomain}}/.well-known/assetlinks.json
```

This route should serve a static JSON array containing your app's package name and SHA-256 certificate fingerprint:

```json .well-known/assetlinks.json theme={"system"}
[{
  "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.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"
    ]
  }
}]
```

Replace:

* `com.example.yourapp` with your app's package name
* The SHA-256 fingerprint with your app's signing certificate fingerprint

### Get Your SHA-256 Certificate Fingerprint

To get your app's SHA-256 certificate fingerprint:

```bash theme={"system"}
# For debug keystore
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

# For release keystore
keytool -list -v -keystore /path/to/your/release.keystore -alias your-key-alias
```

Look for the `SHA256` line in the output and copy the fingerprint.

<Note>
  You can validate your asset links configuration using the [Google Digital Asset Links Tool](https://developers.google.com/digital-asset-links/tools/generator). This tool tests your domain against Android's requirements and helps ensure your passkey setup is properly configured.
</Note>

### SDK Configuration

Configure the `appOrigin` in your SDK initialization to associate passkeys with your domain:

```kotlin MainActivity.kt theme={"system"}
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.core.ClientProps

val props = ClientProps(
    environmentId = "your-environment-id",
    appName = "Your App",
    appOrigin = "https://yourdomain.com"  // Replace with your actual domain
)
DynamicSDK.initialize(props, applicationContext, this)
```

<Warning>
  The `appOrigin` must match your website domain exactly (e.g., `https://example.com`). This domain will be used to create and associate passkeys, so ensure it's the same domain where you host the `.well-known/assetlinks.json` endpoint.
</Warning>

## Managing Passkeys

Now that your application has correctly associated your domain, you can register and authenticate using passkeys.

### Sign In with Passkey

Use passkeys as the primary authentication method:

```kotlin theme={"system"}
import com.dynamic.sdk.android.DynamicSDK

val sdk = DynamicSDK.getInstance()

fun signInWithPasskey() {
    try {
        sdk.auth.passkey.signIn()
        println("Signed in with passkey!")
    } catch (e: Exception) {
        println("Passkey sign-in failed: ${e.message}")
    }
}
```

When called, this triggers the system passkey prompt where users authenticate with biometrics, device PIN, or pattern.

## Register passkey

Register a new passkey for the authenticated user:

```kotlin theme={"system"}
sdk.passkeys.registerPasskey()
```

This will trigger the device's biometric authentication flow (fingerprint, face recognition, or device PIN).

## Get user passkeys

Retrieve all passkeys registered for the current user:

```kotlin theme={"system"}
import com.dynamic.sdk.android.Models.UserPasskey

val passkeys: List<UserPasskey> = sdk.passkeys.getPasskeys()

passkeys.forEach { passkey ->
    println("Passkey ID: ${passkey.id}")
    println("Created: ${passkey.createdAt}")
    println("Last used: ${passkey.lastUsedAt}")
    println("Is default: ${passkey.isDefault}")
}
```

## Authenticate with passkey for MFA

Use a passkey to authenticate for MFA purposes:

```kotlin theme={"system"}
import com.dynamic.sdk.android.Models.MfaCreateToken

val response = sdk.passkeys.authenticatePasskeyMFA(
    createMfaToken = MfaCreateToken(singleUse = true),
    relatedOriginRpId = null
)

println("Verified: ${response.verified}")
```

## Delete passkey

Remove a passkey from the user's account:

```kotlin theme={"system"}
import com.dynamic.sdk.android.Models.DeletePasskeyRequest

val passkeyId = "passkey-id"
sdk.passkeys.deletePasskey(DeletePasskeyRequest(passkeyId = passkeyId))
```

## Complete passkey management example

Here's a complete example of passkey management in a ViewModel:

```kotlin theme={"system"}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.UserPasskey
import com.dynamic.sdk.android.Models.DeletePasskeyRequest
import com.dynamic.sdk.android.Models.MfaCreateToken
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class PasskeysViewModel(private val sdk: DynamicSDK) : ViewModel() {
    private val _passkeys = MutableStateFlow<List<UserPasskey>>(emptyList())
    val passkeys: StateFlow<List<UserPasskey>> = _passkeys

    private val _isLoading = MutableStateFlow(false)
    val isLoading: StateFlow<Boolean> = _isLoading

    private val _error = MutableStateFlow<String?>(null)
    val error: StateFlow<String?> = _error

    init {
        loadPasskeys()
    }

    fun loadPasskeys() {
        viewModelScope.launch {
            _isLoading.value = true
            _error.value = null
            try {
                _passkeys.value = sdk.passkeys.getPasskeys()
            } catch (e: Exception) {
                _error.value = "Failed to load passkeys: ${e.message}"
            }
            _isLoading.value = false
        }
    }

    fun registerPasskey(onSuccess: () -> Unit) {
        viewModelScope.launch {
            _isLoading.value = true
            _error.value = null
            try {
                sdk.passkeys.registerPasskey()
                loadPasskeys()
                onSuccess()
            } catch (e: Exception) {
                _error.value = "Failed to register passkey: ${e.message}"
            }
            _isLoading.value = false
        }
    }

    fun authenticateMfa(onSuccess: (String?) -> Unit) {
        viewModelScope.launch {
            _isLoading.value = true
            _error.value = null
            try {
                val response = sdk.passkeys.authenticatePasskeyMFA(
                    createMfaToken = MfaCreateToken(singleUse = true),
                    relatedOriginRpId = null
                )
                onSuccess(response.verified.toString())
            } catch (e: Exception) {
                _error.value = "Failed to authenticate: ${e.message}"
            }
            _isLoading.value = false
        }
    }

    fun deletePasskey(passkeyId: String, onSuccess: () -> Unit) {
        viewModelScope.launch {
            _isLoading.value = true
            _error.value = null
            try {
                sdk.passkeys.deletePasskey(DeletePasskeyRequest(passkeyId = passkeyId))
                loadPasskeys()
                onSuccess()
            } catch (e: Exception) {
                _error.value = "Failed to delete passkey: ${e.message}"
            }
            _isLoading.value = false
        }
    }
}
```

## Passkey UI example

Display passkeys in a Compose UI:

```kotlin theme={"system"}
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun PasskeysScreen(viewModel: PasskeysViewModel) {
    val passkeys by viewModel.passkeys.collectAsState()
    val isLoading by viewModel.isLoading.collectAsState()
    val error by viewModel.error.collectAsState()

    Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
        Button(
            onClick = { viewModel.registerPasskey { /* Success */ } },
            modifier = Modifier.fillMaxWidth()
        ) {
            Icon(Icons.Default.Add, contentDescription = null)
            Spacer(modifier = Modifier.width(8.dp))
            Text("Register Passkey")
        }

        Spacer(modifier = Modifier.height(16.dp))

        error?.let { errorMessage ->
            Text(
                text = errorMessage,
                color = MaterialTheme.colorScheme.error,
                style = MaterialTheme.typography.bodySmall
            )
            Spacer(modifier = Modifier.height(8.dp))
        }

        if (isLoading) {
            CircularProgressIndicator()
        } else if (passkeys.isEmpty()) {
            Text("No passkeys registered")
        } else {
            LazyColumn {
                items(passkeys) { passkey ->
                    PasskeyItem(
                        passkey = passkey,
                        onDelete = { 
                            viewModel.deletePasskey(passkey.id) { /* Success */ }
                        },
                        onAuthenticate = {
                            viewModel.authenticateMfa { token ->
                                println("MFA Token: $token")
                            }
                        }
                    )
                }
            }
        }
    }
}

@Composable
fun PasskeyItem(
    passkey: UserPasskey,
    onDelete: () -> Unit,
    onAuthenticate: () -> Unit
) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 8.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = "Passkey",
                style = MaterialTheme.typography.titleMedium
            )
            Text(
                text = "ID: ${passkey.id.take(8)}...",
                style = MaterialTheme.typography.bodySmall
            )
            Text(
                text = "Created: ${passkey.createdAt.take(10)}",
                style = MaterialTheme.typography.bodySmall
            )
            
            if (passkey.isDefault == true) {
                Text(
                    text = "Default",
                    style = MaterialTheme.typography.labelSmall,
                    color = MaterialTheme.colorScheme.primary
                )
            }

            Spacer(modifier = Modifier.height(8.dp))

            Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                Button(onClick = onAuthenticate) {
                    Text("Authenticate")
                }
                OutlinedButton(onClick = onDelete) {
                    Text("Delete")
                }
            }
        }
    }
}
```

## Using Passkeys for MFA

Passkeys can also be used as a second factor for multi-factor authentication. Once you've completed the configuration above, see the [MFA Guide](/kotlin/mfa) for details on using passkeys as a 2FA method.

## Benefits of Passkeys

* **Passwordless**: No need to remember or type passwords
* **Secure**: Uses device biometrics or PIN
* **Phishing-resistant**: Cannot be stolen or intercepted
* **Fast**: Quick authentication with fingerprint or face recognition
* **Cross-platform**: Works across devices with Google Password Manager sync

## Troubleshooting

### Common Issues

**Passkey registration fails**

* Verify your asset links configuration at `https://yourdomain.com/.well-known/assetlinks.json`
* Check that the JSON contains your correct package name and SHA-256 fingerprint
* Ensure you're using the correct signing certificate fingerprint (debug vs release)
* Validate your asset links using the [Google Digital Asset Links Tool](https://developers.google.com/digital-asset-links/tools/generator)

**"No passkeys available" error**

* The user may not have any passkeys registered yet
* Ensure the user is authenticated before attempting passkey sign-in
* Check that passkeys are supported on the device (Android 9.0+)

**Domain mismatch errors**

* Verify `appOrigin` in SDK configuration matches your asset links domain exactly
* Check that both use the same protocol (https\://)
* Ensure the domain is accessible and the asset links file is served correctly

## What's Next

Now that you have passkeys configured:

1. **[MFA Guide](/kotlin/mfa)** - Use passkeys as a second factor for multi-factor authentication
2. **[Authentication](/kotlin/authentication)** - Learn about other authentication methods
3. **[Session Management](/kotlin/session-management)** - Manage authenticated sessions
