> ## 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 Swift SDK.

The Swift SDK provides step-up authentication through the `StepUpAuthModule`, accessible via `DynamicSDK.instance().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**.

```swift theme={"system"}
import DynamicSDKSwift

let sdk = DynamicSDK.instance()

func exportPrivateKey() async throws {
    // 1. Check if step-up is required
    let isRequired = try await sdk.stepUpAuth.isStepUpRequired(scope: "wallet:export")

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

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

## Checking step-up requirements

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

```swift theme={"system"}
let sdk = DynamicSDK.instance()

let isRequired = try await sdk.stepUpAuth.isStepUpRequired(scope: "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.

### promptStepUpAuth

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

```swift theme={"system"}
let token = try await sdk.stepUpAuth.promptStepUpAuth(
    requestedScopes: ["wallet:export"]
)
```

### promptMfa

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

```swift theme={"system"}
let token = try await sdk.stepUpAuth.promptMfa(
    requestedScopes: ["wallet:export"]
)
```

### promptReauthenticate

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

```swift theme={"system"}
let token = try await sdk.stepUpAuth.promptReauthenticate(
    requestedScopes: ["wallet:export"]
)
```

## Individual verification methods

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

### Email/SMS OTP

```swift theme={"system"}
let sdk = DynamicSDK.instance()

// 1. Send the OTP
let otpResult = try await sdk.stepUpAuth.sendOtp()

// 2. Collect the code from the user, then verify
let result = try await sdk.stepUpAuth.verifyOtp(
    verificationToken: userEnteredCode,
    requestedScopes: ["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.

```swift theme={"system"}
try await sdk.stepUpAuth.verifyWallet(
    requestedScopes: ["wallet:export"]
)
```

### Passkey MFA

```swift theme={"system"}
let result = try await sdk.stepUpAuth.verifyPasskeyMfa(
    requestedScopes: ["wallet:export"]
)
```

### TOTP MFA

```swift theme={"system"}
let result = try await sdk.stepUpAuth.verifyTotpMfa(
    code: "123456",
    requestedScopes: ["wallet:export"]
)

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

### Recovery code

```swift theme={"system"}
let result = try await sdk.stepUpAuth.verifyRecoveryCode(
    code: "recovery-code-here",
    requestedScopes: ["wallet:export"]
)
```

## Resetting state

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

```swift theme={"system"}
try await sdk.stepUpAuth.resetState()
```

## Full example

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

```swift theme={"system"}
import SwiftUI
import DynamicSDKSwift

struct StepUpAuthExample: View {
    @StateObject private var vm = StepUpAuthExampleViewModel()

    var body: some View {
        VStack(spacing: 16) {
            Button {
                Task { await vm.checkRequired() }
            } label: {
                Text("Is Step-Up Required?")
            }

            Button {
                Task { await vm.exportWallet() }
            } label: {
                Text(vm.isLoading ? "Verifying..." : "Export Wallet")
            }
            .disabled(vm.isLoading)

            Button {
                Task { await vm.promptMfa() }
            } label: {
                Text("Prompt MFA")
            }
            .disabled(vm.isLoading)

            Button {
                Task { await vm.promptReauthenticate() }
            } label: {
                Text("Prompt Reauthenticate")
            }
            .disabled(vm.isLoading)

            if let result = vm.resultMessage {
                Text(result)
                    .foregroundColor(.green)
            }

            if let error = vm.errorMessage {
                Text(error)
                    .foregroundColor(.red)
            }
        }
        .padding()
    }
}

@MainActor
final class StepUpAuthExampleViewModel: ObservableObject {
    @Published var isLoading = false
    @Published var resultMessage: String?
    @Published var errorMessage: String?

    private let sdk = DynamicSDK.instance()

    func checkRequired() async {
        do {
            let required = try await sdk.stepUpAuth.isStepUpRequired(scope: "wallet:export")
            resultMessage = required
                ? "Step-up authentication IS required"
                : "Step-up authentication is NOT required"
        } catch {
            errorMessage = "Failed to check: \(error)"
        }
    }

    func exportWallet() async {
        isLoading = true
        resultMessage = nil
        errorMessage = nil
        defer { isLoading = false }

        do {
            let required = try await sdk.stepUpAuth.isStepUpRequired(scope: "wallet:export")

            if required {
                _ = try await sdk.stepUpAuth.promptStepUpAuth(
                    requestedScopes: ["wallet:export"]
                )
            }

            // Token is now stored — proceed with export
            // try await performExport()
            resultMessage = "Export completed"
        } catch {
            errorMessage = "Step-up auth failed: \(error)"
        }
    }

    func promptMfa() async {
        isLoading = true
        resultMessage = nil
        errorMessage = nil
        defer { isLoading = false }

        do {
            let token = try await sdk.stepUpAuth.promptMfa(
                requestedScopes: ["wallet:export"]
            )
            resultMessage = "MFA token: \(token ?? "nil")"
        } catch {
            errorMessage = "MFA prompt failed: \(error)"
        }
    }

    func promptReauthenticate() async {
        isLoading = true
        resultMessage = nil
        errorMessage = nil
        defer { isLoading = false }

        do {
            let token = try await sdk.stepUpAuth.promptReauthenticate(
                requestedScopes: ["wallet:export"]
            )
            resultMessage = "Reauthenticate token: \(token ?? "nil")"
        } catch {
            errorMessage = "Reauthentication failed: \(error)"
        }
    }
}
```

## Error handling

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

```swift theme={"system"}
do {
    _ = try await sdk.stepUpAuth.promptStepUpAuth(
        requestedScopes: ["wallet:export"]
    )
} catch {
    // User cancelled or verification failed
    print("Step-up auth failed: \(error)")
}
```

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

## 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, and SDK implementation guides for every supported client
* [Authentication screens](/swift/headless-authentication) — Headless checklist (step-up alongside email, MFA, and other flows)
* [Device registration](/swift/device-registration) — Required for headless apps on API `2026_04_01` together with step-up
* [MFA](/swift/mfa) — Swift MFA guide
* [Passkey Setup](/swift/passkey-setup) — Configure passkeys for your iOS app
