---
title: Using device identifiers on React Native
description: Use device identifiers in React Native with the PingOne Advanced Identity Cloud Device Profiling module, and read the identifier independently using getDeviceId()
component: orchsdks
page_id: orchsdks:journey:use-cases/device-profiling/react-native-device-ids
canonical_url: https://developer.pingidentity.com/orchsdks/journey/use-cases/device-profiling/react-native-device-ids.html
llms_txt: https://developer.pingidentity.com/orchsdks/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
keywords: ["Device", "Hardware", "Source Code", "Integration", "SDK", "Android"]
section_ids:
  how_device_identifiers_work: How device identifiers work
  ios_defaults: iOS defaults
  android_defaults: Android defaults
  configuring_device_identifiers: Configuring device identifiers
  attaching_a_logger_for_identifier_diagnostics: Attaching a logger for identifier diagnostics
  customizing_the_default_identifier_on_ios: Customizing the default identifier on iOS
  customizing_the_keychain_account: Customizing the Keychain account
  regenerating_the_identifier: Regenerating the identifier
  falling_back_to_uuid_based_identifiers: Falling back to UUID-based identifiers
  creating_a_custom_ios_identifier: Creating a custom iOS identifier
  customizing_the_default_identifier_on_android: Customizing the default identifier on Android
  built_in_android_identifier_strategies: Built-in Android identifier strategies
  switching_to_androididdeviceidentifier: Switching to AndroidIDDeviceIdentifier
  custom_android_identifier: Custom Android identifier
  exposing_native_identifiers_to_javascript: Exposing native identifiers to JavaScript
---

# Using device identifiers on React Native

[icon: circle-check, set=far]PingOne Advanced Identity Cloud [icon: circle-check, set=far]PingAM [icon: react, set=fab]React Native

The **Device Profiling** module includes a device identifier in the collected profile. On both Android and iOS, the native SDK uses the same secure identifier generated by the **Device ID** module.

In React Native, you do not need to call `@ping-identity/rn-device-id`. The identifier is resolved automatically by the native layer when you call `collectDeviceProfile`. However, if you need to read the same identifier independently (for logging or display), you can use `getDeviceId()` from `@ping-identity/rn-device-id` directly.

Device identifiers serve two distinct roles in the SDK:

* **Device profiling**

  A unique identifier is embedded in the device profile payload sent to the server when a `DeviceProfileCallback`, `PingOneProtectInitializeCallback`, or `PingOneProtectEvaluationCallback` is processed.

* **Device binding**

  A per-user key identifier (kid) is generated and stored alongside the cryptographic key pair when `bindForJourney()` registers a device.

Both roles are implemented in the underlying native platform SDKs. The React Native modules delegate identifier generation and storage to those native implementations. Understanding the platform defaults — and knowing when and how to customize them — helps you control identifier stability, sharing across apps, and recovery behavior.

## How device identifiers work

The Orchestration SDK for React Native wraps native Android and iOS implementations. Device identifier generation happens entirely in native code; the JavaScript layer passes the identifier transparently as part of the profile or binding payload.

The table provides a platform comparison summary.

| Characteristic                 | iOS                                                  | Android                                               |
| ------------------------------ | ---------------------------------------------------- | ----------------------------------------------------- |
| Default identifier source      | Keychain-backed RSA key pair (SHA-256 of public key) | Android KeyStore RSA key pair (SHA-256 of public key) |
| Persists across app reinstall  | Yes                                                  | No — regenerated on reinstall                         |
| Persists across device restore | Yes (from encrypted backup)                          | No                                                    |
| Configurable from JavaScript   | No — configure in native Xcode project               | No — configure in native Android project              |
| Alternate built-in strategy    | `UUIDDeviceIdentifier`                               | `AndroidIDDeviceIdentifier`, `LegacyDeviceIdentifier` |
| Custom identifier support      | Yes — implement `DeviceIdentifier` protocol          | Yes — implement `DeviceIdentifier` interface          |
| Cross-app sharing              | Yes — via Keychain Access Group                      | Yes — via `AndroidIDDeviceIdentifier` (API 26+)       |

### iOS defaults

On iOS, the default device identifier is a SHA-256 hash of a public key generated and stored in the Keychain. The Keychain persists across app reinstalls (unless the device is wiped), giving the identifier strong stability.

**Default identifier persistence behavior on iOS**

| Scenario                             | Behavior                                                                                                                                                            |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| App uninstall and reinstall          | The identifier **persists**. The iOS Keychain is not cleared when an app is deleted, so the reinstalled app retrieves the same key.                                 |
| App data cleared                     | This is not a standard user action on iOS. App reinstall is the closest equivalent, which preserves the identifier as noted above.                                  |
| Device backup and restore            | The identifier **persists** if restored from an encrypted iCloud or local backup, because these backups include Keychain data.                                      |
| Factory reset                        | The identifier is **permanently deleted**. A factory reset clears all device storage, including the Keychain.                                                       |
| Sharing across apps (same developer) | The identifier **can be shared** across apps from the same developer using a Keychain Access Group. Configure the `keychainAccount` property in the native iOS SDK. |

### Android defaults

On Android, the default device identifier uses the Android KeyStore to generate and store an RSA key pair. The SHA-256 hash of the public key is the identifier.

**Default identifier persistence behavior on Android**

| Scenario                                         | Behavior                                                                                                                                     |
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| App uninstall and reinstall                      | The identifier is **regenerated**. Android KeyStore entries are scoped to the app's UID, which changes on reinstall.                         |
| App data cleared (**Settings** > **Clear Data**) | The identifier is **regenerated**. Clearing app data removes the KeyStore entry.                                                             |
| Device backup and restore                        | The identifier is **not restored**. Android KeyStore entries are not included in standard backups.                                           |
| Factory reset                                    | The identifier is **permanently deleted**.                                                                                                   |
| Sharing across apps                              | Not supported with the default KeyStore strategy. Use `AndroidIDDeviceIdentifier` (ANDROID\_ID) if cross-app sharing is required on API 26+. |

|   |                                                                                                                                                                                                                                                                                                                    |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|   | The persistence difference between iOS (persists across reinstalls) and Android (regenerated on reinstall) is important for server-side logic. If your Protect policy or device management rules depend on identifier stability, design for the less stable Android behavior to ensure cross-platform consistency. |

## Configuring device identifiers

Device identifier behavior in the React Native SDK is controlled by the underlying native platform modules. JavaScript configuration options are limited to concerns that are visible at the JS bridge boundary — primarily logging. Native-level customization (identifier strategy, Keychain account, KeyStore parameters) requires editing the native project files.

### Attaching a logger for identifier diagnostics

Both `collectDeviceProfile` and `createBindingClient` accept a `LoggerInstance`.

Attaching a logger gives you visibility into identifier generation and retrieval operations:

```typescript
import { collectDeviceProfile } from '@ping-identity/rn-device-profile';
import { createBindingClient } from '@ping-identity/rn-binding';
import { logger } from '@ping-identity/rn-logger';

const sdkLogger = logger({ level: 'debug' });

// Device profiling — logger shows native identifier resolution
const profile = await collectDeviceProfile(['platform', 'hardware', 'network'], {
  logger: sdkLogger,
});

// Device binding — logger shows key generation and identifier storage
const bindingClient = createBindingClient({
  logger: sdkLogger,
});
```

## Customizing the default identifier on iOS

The iOS SDK's `DefaultDeviceIdentifier` uses a Keychain-backed RSA key pair. To customize its configuration — such as sharing it across apps using a Keychain Access Group, changing the account name, or adjusting the key size — edit the native iOS module initialization in your Xcode project.

### Customizing the Keychain account

By default the SDK stores the identifier under a Keychain account derived from your bundle ID.

To share the identifier across apps from the same developer, configure a shared Keychain Access Group:

```swift
// In your iOS AppDelegate or a native module initializer
import PingDeviceId

let config = DeviceIdentifierConfiguration(
    keychainAccount: "com.example.shared.deviceid",  // shared across your apps
    useEncryption: true,
    keySize: 2048
)

let deviceIdentifier = try DefaultDeviceIdentifier(configuration: config)
```

Pass this configured instance to the native SDK before the React Native bridge initializes the modules.

|   |                                                                                                                                                                |
| - | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | Do not disable encryption (`useEncryption: false`) in a production app.Disabling encryption removes the security guarantees of the Keychain-backed identifier. |

### Regenerating the identifier

To force generation of a new identifier — for example, after a security incident or when a user explicitly requests device removal — call `regenerateIdentifier()` in native code:

```swift
// In a native module exposed to React Native
import PingDeviceId

func resetDeviceIdentifier() async throws -> String {
    let deviceIdentifier = try DefaultDeviceIdentifier()
    let newId = try await deviceIdentifier.regenerateIdentifier()
    print("New Device ID: \(newId)")
    return newId
}
```

### Falling back to UUID-based identifiers

If Keychain operations fail on certain devices (for example, shared enterprise devices with restricted Keychain access), you can fall back to a simpler UUID-based identifier:

```swift
import PingDeviceId

// UUIDDeviceIdentifier stores a UUID in UserDefaults rather than the Keychain
let deviceIdentifier = try UUIDDeviceIdentifier()
let id = try await deviceIdentifier.id
```

`UUIDDeviceIdentifier` is less stable than the Keychain-backed default: the UUID is cleared when the app's data is deleted. Use it only when Keychain access is not reliably available.

### Creating a custom iOS identifier

For advanced use cases, implement the `DeviceIdentifier` protocol to supply a completely custom identifier:

```swift
import PingDeviceId

struct CustomDeviceIdentifier: DeviceIdentifier {
    var id: String {
        get async throws {
            // Return your custom stable identifier.
            // Ensure persistence — return the same value across app launches.
            return try await loadOrGenerateStableId()
        }
    }
}
```

Register the custom implementation with the device profile or binding modules before the app starts processing journeys.

## Customizing the default identifier on Android

The Android SDK provides three built-in identifier strategies and supports custom implementations.

### Built-in Android identifier strategies

| Strategy                    | Persistence after reinstall | Description                                                                                                                                                                     |
| --------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DefaultDeviceIdentifier`   | Regenerated                 | Uses Android KeyStore. Most secure. The default when no strategy is specified.                                                                                                  |
| `AndroidIDDeviceIdentifier` | Persists (API 26+)          | Uses `Settings.Secure.ANDROID_ID`. Persists across reinstalls on Android 8.0+ because the value is scoped to the signing certificate and user account. Resets on factory reset. |
| `LegacyDeviceIdentifier`    | Varies                      | Backward-compatible strategy for apps migrating from older SDK versions. Use only when continuity with pre-migration identifiers is required.                                   |

### Switching to `AndroidIDDeviceIdentifier`

To use `ANDROID_ID` as the device identifier — for example, to maintain a stable identifier across reinstalls — configure the native Android module:

```kotlin
// In your Android Application subclass or native module setup
import com.pingidentity.deviceprofile.DefaultDeviceIdentifier
import com.pingidentity.deviceprofile.AndroidIDDeviceIdentifier

// Replace the default strategy with ANDROID_ID
val deviceIdentifier = AndroidIDDeviceIdentifier(context)
```

|   |                                                                                                                                                                                                                     |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | `ANDROID_ID` is unique per user account and signing certificate on Android 8.0 (API 26) and later. On earlier API levels it may not be reliable. Verify your minimum API requirement before adopting this strategy. |

### Custom Android identifier

To supply a completely custom identifier on Android, implement the `DeviceIdentifier` interface:

```kotlin
import com.pingidentity.deviceprofile.DeviceIdentifier

class CustomDeviceIdentifier(private val context: Context) : DeviceIdentifier {
    override suspend fun id(): String {
        // Return your custom stable identifier.
        // SHA-256 hash of custom data is recommended for uniformity.
        val customData = "org.example.${Build.SERIAL}"
        return sha256(customData)
    }

    private fun sha256(input: String): String {
        val digest = MessageDigest.getInstance("SHA-256")
        val hash = digest.digest(input.toByteArray(Charsets.UTF_8))
        return hash.joinToString("") { "%02x".format(it) }
    }
}
```

Register the implementation with the native SDK before the React Native bridge initializes.

## Exposing native identifiers to JavaScript

If your application needs the device identifier value in JavaScript — for example, to display it, log it server-side, or include it in a custom API call — use the `getDeviceId` function exported directly from the `@ping-identity/rn-device-id`:

```typescript
import { getDeviceId } from '@ping-identity/rn-device-id';

export async function getDeviceId(): Promise<string> {
  try {
    const deviceId = await getDeviceId();
    // Use deviceId to display, log server-side, or include in a custom API call
  } catch (error) {
    if (error instanceof DeviceIdError) {
      console.error('Failed to retrieve device ID:', error.message);
    }
  }
}
```

```swift
// iOS: MyDeviceIdModule.swift
@objc(MyDeviceIdModule)
class MyDeviceIdModule: NSObject {
  @objc
  func getDeviceId(_ resolve: @escaping RCTPromiseResolveBlock,
                   reject: @escaping RCTPromiseRejectBlock) {
    Task {
      do {
        let identifier = try DefaultDeviceIdentifier()
        let id = try await identifier.id
        resolve(id)
      } catch {
        reject("DEVICE_ID_ERROR", error.localizedDescription, error)
      }
    }
  }
}
```

```kotlin
// Android: MyDeviceIdModule.kt
class MyDeviceIdModule(reactContext: ReactApplicationContext)
    : ReactContextBaseJavaModule(reactContext) {

  override fun getName() = "MyDeviceIdModule"

  @ReactMethod
  fun getDeviceId(promise: Promise) {
    CoroutineScope(Dispatchers.IO).launch {
      try {
        val id = DefaultDeviceIdentifier.id()
        promise.resolve(id)
      } catch (e: Exception) {
        promise.reject("DEVICE_ID_ERROR", e.message, e)
      }
    }
  }
}
```
