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

# Step-up authentication

> Require users to re-verify their identity before sensitive actions using elevated access tokens in the Kotlin SDK.

The Kotlin SDK provides step-up authentication through the `StepUpAuthModule`, accessible via `DynamicSDK.getInstance().stepUpAuth`. You can check requirements, prompt built-in UI flows, or call individual verification methods for a fully custom experience.

After verification, the elevated access token is automatically stored and applied to subsequent API calls. You never need to manually handle the token.

For concepts, scopes, and token lifecycle, see [Step-up authentication overview](/overview/authentication/step-up-auth).

## Prerequisites

* `DynamicSDK` initialized with your environment ID
* At least one verification method enabled in your [dashboard security settings](https://app.dynamic.xyz/dashboard/security)
* Step-up authentication enabled for your environment

## Quick start

The pattern is: **check → verify → proceed**.

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

val sdk = DynamicSDK.getInstance()

suspend fun exportPrivateKey() {
    // 1. Check if step-up is required
    val isRequired = sdk.stepUpAuth.isStepUpRequired("wallet:export")

    if (isRequired) {
        // 2. Prompt the user — auto-routes to MFA or re-auth
        sdk.stepUpAuth.promptStepUpAuth(
            requestedScopes = listOf("wallet:export")
        )
    }

    // 3. Proceed — token is attached automatically
    performExport()
}
```

## Checking step-up requirements

Use `isStepUpRequired` to check whether the user needs to re-verify for a given scope:

```kotlin theme={"system"}
val sdk = DynamicSDK.getInstance()

val isRequired = sdk.stepUpAuth.isStepUpRequired("wallet:export")

if (isRequired) {
    // Trigger verification
}
```

## Prompt methods (built-in UI)

These methods show Dynamic's built-in UI for step-up authentication. The SDK handles method selection and user interaction. All methods are `suspend` functions — call them from a coroutine scope.

### promptStepUpAuth

Automatically chooses between MFA and re-authentication based on the user's configuration. This is the recommended approach.

```kotlin theme={"system"}
val token = sdk.stepUpAuth.promptStepUpAuth(
    requestedScopes = listOf("wallet:export")
)
```

### promptMfa

Explicitly prompts the user with MFA methods (passkey or TOTP). Use this when you know the user has MFA configured.

```kotlin theme={"system"}
val token = sdk.stepUpAuth.promptMfa(
    requestedScopes = listOf("wallet:export")
)
```

### promptReauthenticate

Explicitly prompts the user to re-authenticate using a non-MFA method (email OTP, SMS OTP, or external wallet signature).

```kotlin theme={"system"}
val token = sdk.stepUpAuth.promptReauthenticate(
    requestedScopes = listOf("wallet:export")
)
```

## Individual verification methods

For custom UI implementations, you can call individual verification methods directly.

### Email/SMS OTP

```kotlin theme={"system"}
val sdk = DynamicSDK.getInstance()

// 1. Send the OTP
val otpResult = sdk.stepUpAuth.sendOtp()

// 2. Collect the code from the user, then verify
val result = sdk.stepUpAuth.verifyOtp(
    verificationToken = userEnteredCode,
    requestedScopes = listOf("wallet:export")
)
```

### Wallet signature (external wallets only)

Wallet-based step-up verification is only available for external wallets. Embedded wallets cannot be used for step-up authentication.

```kotlin theme={"system"}
sdk.stepUpAuth.verifyWallet(
    requestedScopes = listOf("wallet:export")
)
```

### Passkey MFA

```kotlin theme={"system"}
val result = sdk.stepUpAuth.verifyPasskeyMfa(
    requestedScopes = listOf("wallet:export")
)
```

### TOTP MFA

```kotlin theme={"system"}
val result = sdk.stepUpAuth.verifyTotpMfa(
    code = "123456",
    requestedScopes = listOf("wallet:export")
)

// Optionally specify a device ID if the user has multiple TOTP devices
val result = sdk.stepUpAuth.verifyTotpMfa(
    code = "123456",
    deviceId = "device-id",
    requestedScopes = listOf("wallet:export")
)
```

### Recovery code

```kotlin theme={"system"}
val result = sdk.stepUpAuth.verifyRecoveryCode(
    code = "recovery-code-here",
    requestedScopes = listOf("wallet:export")
)
```

## Resetting state

Reset the step-up authentication state when needed (for example, when the user navigates away):

```kotlin theme={"system"}
sdk.stepUpAuth.resetState()
```

## Full example

Here's a complete Jetpack Compose example that checks step-up requirements and prompts the user:

```kotlin theme={"system"}
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.dynamic.sdk.android.DynamicSDK
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

@Composable
fun StepUpAuthExample(viewModel: StepUpAuthExampleViewModel = viewModel()) {
    val isLoading by viewModel.isLoading.collectAsState()
    val resultMessage by viewModel.resultMessage.collectAsState()
    val errorMessage by viewModel.errorMessage.collectAsState()

    Column(
        modifier = Modifier.padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        Button(
            onClick = { viewModel.checkRequired() },
            modifier = Modifier.fillMaxWidth(),
            enabled = !isLoading
        ) {
            Text("Is Step-Up Required?")
        }

        Button(
            onClick = { viewModel.exportWallet() },
            modifier = Modifier.fillMaxWidth(),
            enabled = !isLoading
        ) {
            Text(if (isLoading) "Verifying..." else "Export Wallet")
        }

        Button(
            onClick = { viewModel.promptMfa() },
            modifier = Modifier.fillMaxWidth(),
            enabled = !isLoading
        ) {
            Text("Prompt MFA")
        }

        Button(
            onClick = { viewModel.promptReauthenticate() },
            modifier = Modifier.fillMaxWidth(),
            enabled = !isLoading
        ) {
            Text("Prompt Reauthenticate")
        }

        resultMessage?.let {
            Text(it, color = MaterialTheme.colorScheme.primary)
        }

        errorMessage?.let {
            Text(it, color = MaterialTheme.colorScheme.error)
        }
    }
}

class StepUpAuthExampleViewModel : ViewModel() {
    private val sdk = DynamicSDK.getInstance()

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

    private val _resultMessage = MutableStateFlow<String?>(null)
    val resultMessage: StateFlow<String?> = _resultMessage.asStateFlow()

    private val _errorMessage = MutableStateFlow<String?>(null)
    val errorMessage: StateFlow<String?> = _errorMessage.asStateFlow()

    fun checkRequired() {
        viewModelScope.launch {
            try {
                val required = sdk.stepUpAuth.isStepUpRequired("wallet:export")
                _resultMessage.value = if (required)
                    "Step-up authentication IS required"
                else
                    "Step-up authentication is NOT required"
            } catch (e: Exception) {
                _errorMessage.value = "Failed to check: ${e.message}"
            }
        }
    }

    fun exportWallet() {
        viewModelScope.launch {
            _isLoading.value = true
            _resultMessage.value = null
            _errorMessage.value = null
            try {
                val required = sdk.stepUpAuth.isStepUpRequired("wallet:export")

                if (required) {
                    sdk.stepUpAuth.promptStepUpAuth(
                        requestedScopes = listOf("wallet:export")
                    )
                }

                // Token is now stored — proceed with export
                // performExport()
                _resultMessage.value = "Export completed"
            } catch (e: Exception) {
                _errorMessage.value = "Step-up auth failed: ${e.message}"
            }
            _isLoading.value = false
        }
    }

    fun promptMfa() {
        viewModelScope.launch {
            _isLoading.value = true
            _resultMessage.value = null
            _errorMessage.value = null
            try {
                val token = sdk.stepUpAuth.promptMfa(
                    requestedScopes = listOf("wallet:export")
                )
                _resultMessage.value = "MFA token: ${token ?: "nil"}"
            } catch (e: Exception) {
                _errorMessage.value = "MFA prompt failed: ${e.message}"
            }
            _isLoading.value = false
        }
    }

    fun promptReauthenticate() {
        viewModelScope.launch {
            _isLoading.value = true
            _resultMessage.value = null
            _errorMessage.value = null
            try {
                val token = sdk.stepUpAuth.promptReauthenticate(
                    requestedScopes = listOf("wallet:export")
                )
                _resultMessage.value = "Reauthenticate token: ${token ?: "nil"}"
            } catch (e: Exception) {
                _errorMessage.value = "Reauthentication failed: ${e.message}"
            }
            _isLoading.value = false
        }
    }
}
```

## Error handling

Wrap verification calls in try-catch blocks to handle user cancellations and verification failures:

```kotlin theme={"system"}
try {
    sdk.stepUpAuth.promptStepUpAuth(
        requestedScopes = listOf("wallet:export")
    )
} catch (e: Exception) {
    // User cancelled or verification failed
    println("Step-up auth failed: ${e.message}")
}
```

## API reference

### StepUpAuthModule

| Method                                            | Description                                |
| ------------------------------------------------- | ------------------------------------------ |
| `isStepUpRequired(scope)`                         | Check if step-up is required for a scope   |
| `promptStepUpAuth(requestedScopes)`               | Auto-route to MFA or re-auth UI            |
| `promptMfa(requestedScopes)`                      | Show MFA verification UI                   |
| `promptReauthenticate(requestedScopes)`           | Show re-authentication UI                  |
| `sendOtp()`                                       | Send OTP to the user's verified credential |
| `verifyOtp(verificationToken, requestedScopes)`   | Verify an OTP code                         |
| `verifyWallet(requestedScopes)`                   | Verify via external wallet signature       |
| `verifyPasskeyMfa(requestedScopes)`               | Verify via passkey                         |
| `verifyTotpMfa(code, deviceId?, requestedScopes)` | Verify via TOTP code                       |
| `verifyRecoveryCode(code, requestedScopes)`       | Verify via recovery code                   |
| `resetState()`                                    | Reset step-up auth state                   |

### Parameters

All prompt methods accept optional parameters:

| Parameter         | Type            | Description                                                                                                          |
| ----------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- |
| `requestedScopes` | `List<String>?` | Scopes to request for the elevated access token                                                                      |
| `createMfaToken`  | `Boolean?`      | Deprecated. Use `requestedScopes` instead. See [migration guide](/overview/migrations/api/2026_04_01-mfa-migration). |

All individual verification methods and prompt methods are `suspend` functions and must be called from a coroutine scope.

## Available scopes

See the [complete scopes reference](/overview/authentication/step-up-auth#scopes) for all supported values.

## Related

* [Step-up authentication overview](/overview/authentication/step-up-auth) — Concepts, scopes, token lifecycle
* [MFA](/kotlin/mfa) — Kotlin MFA guide
* [Passkeys](/kotlin/passkeys) — Configure passkeys for your Android app
