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

# Device Registration

> Verify new devices and manage trusted devices for your users in the Kotlin SDK.

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](/overview/security/device-registration).

## Prerequisites

* `DynamicSDK` initialized with your environment ID (see [Quickstart](/kotlin/quickstart))
* Device registration enabled in your environment's settings in the [Dynamic Dashboard](https://app.dynamic.xyz)
* [App Links](https://developer.android.com/training/app-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 and a `StateFlow` that updates when the SDK detects an unrecognized device during authentication.

```kotlin theme={"system"}
import com.dynamic.sdk.android.DynamicSDK
import kotlinx.coroutines.flow.collect

val sdk = DynamicSDK.getInstance()

// Check current state
val isRequired = sdk.deviceRegistration.isDeviceRegistrationRequired

// Observe changes reactively
sdk.deviceRegistration.isDeviceRegistrationRequiredChanges.collect { isRequired ->
    if (isRequired) {
        // Show your custom device verification UI
    }
}
```

In a Composable:

```kotlin theme={"system"}
import androidx.compose.runtime.*
import com.dynamic.sdk.android.DynamicSDK

@Composable
fun DeviceRegistrationBanner() {
    val sdk = DynamicSDK.getInstance()
    val isRequired by sdk.deviceRegistration
        .isDeviceRegistrationRequiredChanges
        .collectAsState(initial = false)

    if (isRequired) {
        Text("Please check your email to verify this device.")
    }
}
```

## Getting registered devices

Retrieve all trusted devices for the current user. Each device is returned as a `JsonObject` with the following fields:

| Field             | Type      | Description                                     |
| ----------------- | --------- | ----------------------------------------------- |
| `id`              | `String`  | The device registration ID                      |
| `createdAt`       | `String`  | ISO date of when the device was registered      |
| `displayText`     | `String?` | A friendly device name (e.g., "Pixel 8")        |
| `type`            | `String?` | The device type (e.g., `"mobile"`, `"desktop"`) |
| `isCurrentDevice` | `Boolean` | Whether this is the device making the request   |

```kotlin theme={"system"}
import com.dynamic.sdk.android.DynamicSDK
import kotlinx.serialization.json.jsonPrimitive

val sdk = DynamicSDK.getInstance()

val devices = sdk.deviceRegistration.getRegisteredDevices()

devices.forEach { device ->
    val displayText = device["displayText"]?.jsonPrimitive?.content ?: "Unknown Device"
    val deviceId = device["id"]?.jsonPrimitive?.content ?: "N/A"
    val isCurrentDevice = device["isCurrentDevice"]?.jsonPrimitive?.content?.toBooleanStrictOrNull() ?: false

    println("$displayText (ID: $deviceId, current: $isCurrentDevice)")
}
```

## Revoking a device

Remove a single trusted device. If the revoked device is the current device, the user will be logged out.

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

sdk.deviceRegistration.revokeRegisteredDevice(
    deviceRegistrationId = "device-registration-id"
)
```

## Revoking all devices

Remove all trusted devices for the current user. This always logs the user out.

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

sdk.deviceRegistration.revokeAllRegisteredDevices()
```

## Full example

Here's a complete Jetpack Compose screen for managing trusted devices:

```kotlin theme={"system"}
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
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
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive

@Composable
fun TrustedDevicesScreen(viewModel: TrustedDevicesViewModel = viewModel()) {
    val devices by viewModel.devices.collectAsState()
    val isLoading by viewModel.isLoading.collectAsState()

    LaunchedEffect(Unit) {
        viewModel.loadDevices()
    }

    Column(modifier = Modifier.padding(16.dp)) {
        when {
            isLoading -> {
                Box(
                    modifier = Modifier.fillMaxWidth(),
                    contentAlignment = Alignment.Center
                ) {
                    CircularProgressIndicator()
                }
            }

            devices.isNullOrEmpty() -> {
                Text("No trusted devices registered")
            }

            else -> {
                LazyColumn(verticalArrangement = Arrangement.spacedBy(12.dp)) {
                    items(devices!!) { device ->
                        TrustedDeviceCard(
                            device = device,
                            onRemove = { viewModel.removeDevice(device) }
                        )
                    }
                }

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

                Button(
                    onClick = { viewModel.removeAllDevices() },
                    modifier = Modifier.fillMaxWidth(),
                    colors = ButtonDefaults.buttonColors(
                        containerColor = MaterialTheme.colorScheme.error
                    )
                ) {
                    Text("Remove All Devices")
                }
            }
        }
    }
}

@Composable
fun TrustedDeviceCard(device: JsonObject, onRemove: () -> Unit) {
    val displayText = device["displayText"]?.jsonPrimitive?.content ?: "Unknown Device"
    val deviceType = device["type"]?.jsonPrimitive?.content
    val createdAt = device["createdAt"]?.jsonPrimitive?.content
    val isCurrentDevice = device["isCurrentDevice"]
        ?.jsonPrimitive?.content?.toBooleanStrictOrNull() ?: false

    Card(modifier = Modifier.fillMaxWidth()) {
        Column(modifier = Modifier.padding(16.dp)) {
            Row(
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                Text(displayText, fontWeight = FontWeight.SemiBold)

                if (isCurrentDevice) {
                    Surface(
                        shape = MaterialTheme.shapes.small,
                        color = MaterialTheme.colorScheme.primary
                    ) {
                        Text(
                            "This Device",
                            style = MaterialTheme.typography.labelSmall,
                            color = MaterialTheme.colorScheme.onPrimary,
                            modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
                        )
                    }
                }
            }

            if (deviceType != null) {
                Text(deviceType, style = MaterialTheme.typography.bodySmall)
            }

            if (createdAt != null) {
                Text(
                    "Registered: $createdAt",
                    style = MaterialTheme.typography.bodySmall,
                    color = MaterialTheme.colorScheme.onSurfaceVariant
                )
            }

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

            Button(
                onClick = onRemove,
                modifier = Modifier.fillMaxWidth(),
                colors = ButtonDefaults.buttonColors(
                    containerColor = MaterialTheme.colorScheme.error
                )
            ) {
                Text("Remove")
            }
        }
    }
}

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

    private val _devices = MutableStateFlow<List<JsonObject>?>(null)
    val devices: StateFlow<List<JsonObject>?> = _devices.asStateFlow()

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

    fun loadDevices() {
        viewModelScope.launch {
            _isLoading.value = true
            try {
                _devices.value = sdk.deviceRegistration.getRegisteredDevices()
            } catch (e: Exception) {
                println("Failed to load devices: ${e.message}")
            }
            _isLoading.value = false
        }
    }

    fun removeDevice(device: JsonObject) {
        val deviceId = device["id"]?.jsonPrimitive?.content ?: return
        viewModelScope.launch {
            try {
                sdk.deviceRegistration.revokeRegisteredDevice(
                    deviceRegistrationId = deviceId
                )
                loadDevices()
            } catch (e: Exception) {
                println("Failed to remove device: ${e.message}")
            }
        }
    }

    fun removeAllDevices() {
        viewModelScope.launch {
            try {
                sdk.deviceRegistration.revokeAllRegisteredDevices()
                loadDevices()
            } catch (e: Exception) {
                println("Failed to remove all devices: ${e.message}")
            }
        }
    }
}
```

## Related

* [Device Registration Overview](/overview/security/device-registration) — How device registration works and why it matters
* [Step-up authentication overview](/overview/authentication/step-up-auth) — Concepts, scopes, token lifecycle
* [Session Management](/kotlin/session-management) — Manage authenticated sessions
* [Security Overview](/overview/security/overview) — Dynamic's security posture
