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

# Multi-Factor Authentication (MFA)

> Add an extra layer of security to your iOS app with multi-factor authentication using TOTP devices and passkeys.

## Overview

Multi-Factor Authentication (MFA) adds an additional security layer to your app by requiring users to provide two forms of verification. The Dynamic Swift SDK supports:

* **TOTP (Time-based One-Time Password)** - Codes from authenticator apps like Google Authenticator or Authy
* **Passkey MFA** - Biometric authentication as a second factor
* **Recovery Codes** - Backup codes for account recovery

<Note>
  MFA must be enabled in your [Dynamic Dashboard](https://app.dynamic.xyz/dashboard/security) under Security settings before you can use it in your app.
</Note>

## Prerequisites

* Dynamic SDK initialized (see [Installation Guide](/swift/quickstart))
* User must be authenticated
* MFA enabled in your Dynamic dashboard
* iOS 15.5+ (iOS 16.0+ for passkey support)

## MFA Device Management

### Get User's MFA Devices

Fetch all MFA devices registered for the current user:

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

let sdk = DynamicSDK.instance()

do {
    let devices = try await sdk.mfa.getUserDevices()
    print("Found \(devices.count) MFA device(s)")

    for device in devices {
        print("📱 Device ID: \(device.id ?? "N/A")")
        print("   Type: \(device.type?.rawValue ?? "Unknown")")
        print("   Created: \(device.createdAt ?? "Unknown")")
    }
} catch {
    print("❌ Failed to fetch MFA devices: \(error)")
}
```

### Add a New TOTP Device

Register a new TOTP device (authenticator app):

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

do {
    let device = try await sdk.mfa.addDevice(type: "totp")

    print("✅ MFA device added")
    print("Secret: \(device.secret)")

    // The device contains a secret that can be displayed as a QR code
    // for users to scan with their authenticator app

} catch {
    print("❌ Failed to add MFA device: \(error)")
}
```

### Verify a New Device

After adding a device, verify it with a TOTP code from your authenticator app:

```swift theme={"system"}
let sdk = DynamicSDK.instance()
let totpCode = "123456" // 6-digit code from authenticator app

do {
    try await sdk.mfa.verifyDevice(totpCode, type: "totp")
    print("✅ Device verified successfully")
} catch {
    print("❌ Device verification failed: \(error)")
}
```

### Authenticate with an Existing Device

Authenticate with an existing MFA device to get an MFA token:

```swift theme={"system"}
let sdk = DynamicSDK.instance()
let deviceId = "device_id"
let totpCode = "123456" // 6-digit code from authenticator app

do {
    let mfaToken = try await sdk.mfa.authenticateDevice(
        params: MfaAuthenticateDevice(
            code: totpCode,
            deviceId: deviceId,
            createMfaToken: MfaCreateToken(singleUse: false)
        )
    )

    print("✅ Device authenticated successfully")
    print("MFA Token: \(mfaToken ?? "nil")")

} catch {
    print("❌ Device authentication failed: \(error)")
}
```

### Delete an MFA Device

Remove an MFA device from the user's account:

```swift theme={"system"}
let sdk = DynamicSDK.instance()
let deviceId = "device_id_to_delete"
let totpCode = "123456"

do {
    // Step 1: Authenticate to get MFA token
    let mfaToken = try await sdk.mfa.authenticateDevice(
        params: MfaAuthenticateDevice(
            code: totpCode,
            deviceId: deviceId,
            createMfaToken: MfaCreateToken(singleUse: true)
        )
    )

    guard let token = mfaToken else {
        print("❌ Failed to get MFA token")
        return
    }

    // Step 2: Delete the device
    try await sdk.mfa.deleteUserDevice(
        deviceId: deviceId,
        mfaAuthToken: token
    )

    print("✅ MFA device deleted")
} catch {
    print("❌ Failed to delete device: \(error)")
}
```

## Recovery Codes

### Get Recovery Codes

After setting up MFA, users should save recovery codes for account recovery:

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

do {
    let recoveryCodes = try await sdk.mfa.getRecoveryCodes(generateNewCodes: false)

    print("✅ Recovery codes retrieved:")
    for (index, code) in recoveryCodes.enumerated() {
        print("\(index + 1). \(code)")
    }

    // Display these codes to the user prominently
    // Encourage them to save codes in a secure location

} catch {
    print("❌ Failed to get recovery codes: \(error)")
}
```

### Acknowledge Recovery Codes

After displaying recovery codes to the user, you should acknowledge that they've been shown:

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

do {
    try await sdk.mfa.acknowledgeRecoveryCodes()
    print("✅ Recovery codes acknowledged")
} catch {
    print("❌ Failed to acknowledge recovery codes: \(error)")
}
```

<Tip>
  **Recovery Code Flow**: When a user sets up MFA for the first time, they'll receive recovery codes. The app should:

  1. Display all recovery codes clearly
  2. Prompt the user to save them securely
  3. Call `acknowledgeRecoveryCodes()` once the user confirms they've saved them
  4. Store the acknowledgment state to prevent showing codes repeatedly
</Tip>

### Generate New Recovery Codes

If recovery codes are lost or compromised:

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

do {
    let newCodes = try await sdk.mfa.getRecoveryCodes(generateNewCodes: true)

    print("✅ New recovery codes generated:")
    for (index, code) in newCodes.enumerated() {
        print("\(index + 1). \(code)")
    }

    // Important: Display these codes to the user and have them save securely

} catch {
    print("❌ Failed to regenerate recovery codes: \(error)")
}
```

### Use Recovery Code for Authentication

If user loses their device, they can use a recovery code:

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

do {
    try await sdk.mfa.authenticateRecoveryCode(code: recoveryCode)
    print("✅ Authenticated with recovery code")
} catch {
    print("❌ Recovery code authentication failed: \(error)")
}
```

## Passkey MFA

Use passkeys as a second factor for enhanced security. Passkeys provide a seamless biometric authentication experience.

<Warning>
  **Configuration Required**: Before using passkeys for MFA, you must configure associated domains and the SDK. See the [Passkey Setup Guide](/swift/passkey-setup) for complete configuration instructions.
</Warning>

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

func authenticateWithPasskeyMFA() async throws -> String? {
    do {
        // Authenticate using passkey as MFA
        let response = try await sdk.passkeys.authenticatePasskeyMFA(
            createMfaToken: MfaCreateToken(singleUse: true),
            relatedOriginRpId: nil
        )

        print("✅ Passkey authenticated for MFA")

        // The response contains a JWT token that can be used for authenticated operations
        return response.jwt

    } catch {
        print("❌ Passkey MFA authentication failed: \(error)")
        throw error
    }
}
```

### Register Passkey as MFA Device

After initial authentication, users can register a passkey as an additional MFA method:

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

func registerPasskeyForMFA() async throws {
    do {
        // Register a new passkey
        try await sdk.passkeys.registerPasskey()
        print("✅ Passkey registered successfully")

        // Now user can authenticate with this passkey as MFA
    } catch {
        print("❌ Failed to register passkey: \(error)")
        throw error
    }
}
```

<Note>
  **Passkey Advantages**: Passkeys offer several benefits over TOTP:

  * No need for a separate authenticator app
  * Biometric authentication (Face ID / Touch ID)
  * More secure against phishing
  * Faster authentication flow
</Note>

## Complete SwiftUI Example

Here's a complete MFA management interface:

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

struct MfaManagementView: View {
    @StateObject private var vm = MfaManagementViewModel()

    var body: some View {
        List {
            // Add Device Section
            Section {
                Button {
                    vm.showingAddDevice = true
                } label: {
                    HStack {
                        Image(systemName: "plus.circle.fill")
                        Text("Add Authenticator App")
                    }
                }
            }

            // Existing Devices
            Section {
                if vm.devices.isEmpty {
                    Text("No MFA devices configured")
                        .foregroundColor(.secondary)
                } else {
                    ForEach(vm.devices, id: \.id) { device in
                        MfaDeviceRow(device: device) {
                            Task { await vm.deleteDevice(device.id) }
                        }
                    }
                }
            } header: {
                Text("Your MFA Devices")
            }

            // Recovery Codes
            Section {
                Button("View Recovery Codes") {
                    Task { await vm.showRecoveryCodes() }
                }

                Button("Regenerate Recovery Codes") {
                    Task { await vm.regenerateCodes() }
                }
            } header: {
                Text("Recovery Options")
            }
        }
        .navigationTitle("MFA Settings")
        .alert("Message", isPresented: .constant(vm.message.isEmpty == false)) {
            Button("OK") { vm.message = "" }
        } message: {
            Text(vm.message)
        }
        .task {
            await vm.loadDevices()
        }
    }
}

@MainActor
class MfaManagementViewModel: ObservableObject {
    @Published var devices: [MfaDevice] = []
    @Published var showingAddDevice = false
    @Published var message = ""

    private let sdk = DynamicSDK.instance()

    func loadDevices() async {
        do {
            devices = try await sdk.mfa.getUserDevices()
        } catch {
            message = "Failed to load devices: \(error.localizedDescription)"
        }
    }

    func showRecoveryCodes() async {
        do {
            let codes = try await sdk.mfa.getRecoveryCodes(generateNewCodes: false)
            message = "Recovery codes:\n" + codes.joined(separator: "\n")
        } catch {
            message = "Failed to get recovery codes: \(error.localizedDescription)"
        }
    }

    func regenerateCodes() async {
        do {
            let codes = try await sdk.mfa.getRecoveryCodes(generateNewCodes: true)
            message = "New recovery codes:\n" + codes.joined(separator: "\n")
        } catch {
            message = "Failed to regenerate recovery codes: \(error.localizedDescription)"
        }
    }

    func deleteDevice(_ deviceId: String?) async {
        guard let deviceId = deviceId else { return }
        // Would prompt for TOTP code first, then authenticate and delete
        message = "Enter TOTP code to delete device"
    }
}

struct MfaDeviceRow: View {
    let device: MfaDevice
    let onDelete: () -> Void

    var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: 4) {
                Text("Authenticator App")
                    .font(.headline)

                Text("Added: \(formatDate(device.createdAt ?? ""))")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }

            Spacer()

            Button(role: .destructive, action: onDelete) {
                Image(systemName: "trash")
                    .foregroundColor(.red)
            }
            .buttonStyle(.borderless)
        }
    }

    func formatDate(_ dateString: String) -> String {
        let formatter = ISO8601DateFormatter()
        guard let date = formatter.date(from: dateString) else {
            return dateString
        }
        let displayFormatter = DateFormatter()
        displayFormatter.dateStyle = .medium
        return displayFormatter.string(from: date)
    }
}
```

## Best Practices

### 1. User Experience

* **Clear instructions**: Guide users through the setup process
* **QR code display**: Make QR codes large and easy to scan
* **Recovery codes**: Emphasize the importance of saving recovery codes
* **Multiple devices**: Allow users to add multiple authenticator apps

### 2. Security

* **Require MFA for sensitive actions**: Prompt for MFA when deleting accounts, changing settings, or performing financial transactions
* **Single-use tokens**: Use `singleUse: true` for sensitive operations
* **Recovery code limits**: Each recovery code can only be used once

### 3. Error Handling

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

do {
    let devices = try await sdk.mfa.getUserDevices()
} catch {
    // Handle common MFA errors
    let errorDesc = error.localizedDescription.lowercased()

    if errorDesc.contains("invalid") {
        print("The code you entered is incorrect")
    } else if errorDesc.contains("expired") {
        print("Code expired, please try again")
    } else {
        print("MFA error: \(error.localizedDescription)")
    }
}
```

## Troubleshooting

### Code Not Working

* **Time sync**: Ensure device time is synchronized (TOTP codes are time-based)
* **Code expired**: TOTP codes expire every 30 seconds
* **Wrong device**: Verify the correct authenticator app is being used

### Can't Delete Last Device

* Users must keep at least one MFA method or disable MFA entirely
* Consider implementing an admin reset flow

### Recovery Codes Not Showing

* Recovery codes are only available after a device is authenticated
* User must authenticate with TOTP code first

## Next Steps

Now that you have MFA set up, you can:

1. **[Passkey Setup](/swift/passkey-setup)** - Configure passkeys for 1FA sign-in or MFA
2. **[Wallet Operations](/swift/wallets/general/token-balances)** - Perform wallet operations
3. **[Session Management](/swift/session-management)** - Manage authenticated sessions

<Note>
  The Swift ExampleApp includes a complete MFA implementation with device management, recovery codes, and passkey integration. See [swift-sdk-and-sample-app/ExampleApp](https://github.com/dynamic-labs/swift-sdk-and-sample-app/tree/main/ExampleApp) for the full code.
</Note>
