Skip to main content
Device registration protects your users from account takeovers by requiring verification when they sign in from an unrecognized device. For a general overview of the feature, see Device Registration.

Prerequisites

  • DynamicSDK initialized with your environment ID (see Quickstart)
  • Device registration enabled in your environment’s settings in the Dynamic Dashboard
  • Universal links configured for your app to handle email verification redirects

Using our UI

Device registration is handled automatically by the SDK. When a user signs in from an unrecognized device, the SDK displays a security prompt with the user’s email, asking them to verify the device. Once the user taps the verification link in their email, the SDK detects the deep link, completes registration, and dismisses the prompt automatically. No additional code is needed — just enable device registration in your dashboard.

Using your UI

Checking if device registration is required

The DeviceRegistrationModule provides a reactive isDeviceRegistrationRequired property that updates when the SDK detects an unrecognized device during authentication.
import SwiftUI
import DynamicSDKSwift
import Combine

struct DeviceRegistrationBanner: View {
    @State private var isRequired = false
    @State private var cancellable: AnyCancellable?

    private let sdk = DynamicSDK.instance()

    var body: some View {
        Group {
            if isRequired {
                VStack(spacing: 8) {
                    Text("New Device Detected")
                        .font(.headline)
                    Text("Please check your email to verify this device.")
                        .foregroundColor(.secondary)
                }
                .padding()
            }
        }
        .onAppear {
            // Check current state
            isRequired = sdk.deviceRegistration.isDeviceRegistrationRequired

            // Listen for changes
            cancellable = sdk.deviceRegistration.isDeviceRegistrationRequiredChanges
                .receive(on: DispatchQueue.main)
                .sink { isRequired = $0 }
        }
        .onDisappear {
            cancellable?.cancel()
        }
    }
}

Getting registered devices

Retrieve all trusted devices for the current user. Each device includes metadata for display. Each device includes:
FieldTypeDescription
idStringThe device registration ID
createdAtStringISO date of when the device was registered
displayTextString?A friendly device name (e.g., “iPhone”)
typeString?The device type (e.g., "mobile", "desktop")
isCurrentDeviceBool?Whether this is the device making the request
import DynamicSDKSwift

let sdk = DynamicSDK.instance()

do {
    let devices = try await sdk.deviceRegistration.getRegisteredDevices()

    for device in devices {
        print("\(device.displayText ?? "Unknown Device")")
        print("  ID: \(device.id)")
        print("  Type: \(device.type ?? "N/A")")
        print("  Current device: \(device.isCurrentDevice == true)")
    }
} catch {
    print("Failed to load devices: \(error)")
}

Revoking a device

Remove a single trusted device. If the revoked device is the current device, the user will be logged out.
let sdk = DynamicSDK.instance()

do {
    try await sdk.deviceRegistration.revokeRegisteredDevice(
        deviceRegistrationId: "device-registration-id"
    )
} catch {
    print("Failed to revoke device: \(error)")
}

Revoking all devices

Remove all trusted devices for the current user. This always logs the user out.
let sdk = DynamicSDK.instance()

do {
    try await sdk.deviceRegistration.revokeAllRegisteredDevices()
} catch {
    print("Failed to revoke all devices: \(error)")
}

Full example

Here’s a complete SwiftUI screen for managing trusted devices:
import SwiftUI
import DynamicSDKSwift

struct TrustedDevicesView: View {
    @StateObject private var vm = TrustedDevicesViewModel()

    var body: some View {
        List {
            if vm.isLoading {
                HStack {
                    Spacer()
                    ProgressView()
                    Spacer()
                }
            } else if let devices = vm.devices {
                if devices.isEmpty {
                    Text("No trusted devices registered")
                        .foregroundColor(.secondary)
                } else {
                    ForEach(devices, id: \.id) { device in
                        VStack(alignment: .leading, spacing: 4) {
                            HStack {
                                Text(device.displayText ?? "Unknown Device")
                                    .fontWeight(.semibold)

                                if device.isCurrentDevice == true {
                                    Text("This Device")
                                        .font(.caption2)
                                        .padding(.horizontal, 6)
                                        .padding(.vertical, 2)
                                        .background(Color.blue.opacity(0.2))
                                        .cornerRadius(4)
                                }
                            }

                            if let type = device.type {
                                Text("Type: \(type)")
                                    .font(.caption)
                                    .foregroundColor(.secondary)
                            }

                            Text("Registered: \(device.createdAt)")
                                .font(.caption)
                                .foregroundColor(.secondary)
                        }
                        .swipeActions {
                            Button("Revoke", role: .destructive) {
                                Task { await vm.revokeDevice(id: device.id) }
                            }
                        }
                    }
                }
            }

            Section {
                Button("Revoke All Devices", role: .destructive) {
                    Task { await vm.revokeAllDevices() }
                }
            }
        }
        .navigationTitle("Trusted Devices")
        .task { await vm.loadDevices() }
        .refreshable { await vm.loadDevices() }
    }
}

@MainActor
final class TrustedDevicesViewModel: ObservableObject {
    @Published var devices: [RegisteredDevice]?
    @Published var isLoading = true

    private let sdk = DynamicSDK.instance()

    func loadDevices() async {
        isLoading = true
        do {
            devices = try await sdk.deviceRegistration.getRegisteredDevices()
        } catch {
            print("Failed to load devices: \(error)")
        }
        isLoading = false
    }

    func revokeDevice(id: String) async {
        do {
            try await sdk.deviceRegistration.revokeRegisteredDevice(
                deviceRegistrationId: id
            )
            await loadDevices()
        } catch {
            print("Failed to revoke device: \(error)")
        }
    }

    func revokeAllDevices() async {
        do {
            try await sdk.deviceRegistration.revokeAllRegisteredDevices()
            await loadDevices()
        } catch {
            print("Failed to revoke all devices: \(error)")
        }
    }
}