Orchestration SDKs

Binding keys to a device in Android

PingOne Advanced Identity Cloud PingAM Android

The Device Binding module provides secure device registration and authentication capabilities for Android applications.

It enables applications to bind cryptographic keys to a device and restrict access to those keys, using biometrics, a PIN, and other authentication methods.

Before you begin

You need to create an authentication journey in your server using the appropriate nodes to enable device binding.

The nodes you can use for device binding Journeys include the follows:

Device Binding node

Allows users to register one or more devices to their account. A user can bind multiple devices, and each device can be bound to multiple users.

The client receives a DeviceBindingCallback when reaching this node in a journey.

Device Signing Verifier node

Verifies possession of a registered bound device.

The node requires the client device to sign a challenge string using the private key that corresponds to the public key stored on the server during initial binding.

The client receives a DeviceSigningVerifierCallback when reaching this node in a journey.

Device Binding Storage node

Optionally persists collected device binding data to a user’s profile in the identity store.

By default, the Device Binding node stores device data in the user’s profile. You can choose instead to store the device data in transient state, perhaps to run a custom script to extract additional context.

In this case, you can use a Device Binding Storage node to store the data in the user’s profile.

This node runs entirely server-side, and doesn’t send a callback to the client.

Securing access to the keys

The Device Binding module supports four distinct methods for accessing the private key, each offering different levels of security and user experience.

You specify which authentication type your client uses in the configuration of the Device Binding node. To change the authentication type to access the keys, you’ll need to rebind the client device

Supported authentication types to access bound keys
  • Biometric Only

  • Biometric with Fallback

  • Application PIN

  • No Authentication

Type name

BIOMETRIC_ONLY

Description

Requires strict biometric authentication with no fallback options

Security level

High

User experience

Streamlined for devices with reliable biometric sensors

Behavior
  • Only accepts biometric authentication, such as a fingerprint, face recognition, or an iris scan

  • Fails immediately if biometric authentication is unavailable or unsuccessful

  • No option to fall back to device PIN, pattern, or password

  • Ideal for high-security applications where biometric verification is mandatory

Use cases

Financial applications, enterprise security, medical applications

Device requirements

Must have functional biometric sensors and enrolled biometric data

Type name

BIOMETRIC_ALLOW_FALLBACK

Description

Prefers biometric authentication but allows fallback to device credentials

Security level

Medium to High

User experience

Flexible with multiple authentication options

Behavior
  • The primary method is a biometric authentication, such as a fingerprint, face recognition, or an iris scan

  • If biometric authentication fails or is unavailable, users can use device credentials

    • Device credentials include a PIN, a pattern, or a password set at the system level

  • Provides better accessibility and usability

Use cases

Consumer applications, general-purpose authentication, accessibility-focused apps

Device requirements
  • Biometric sensors preferred, but not required

  • Must configure the device lock screen

Type name

APPLICATION_PIN

Description

Requires a custom PIN that the application manages entirely

Security level

Medium

User experience

Consistent across all devices regardless of hardware capabilities

Behavior
  • Uses an application-specific PIN separate from device credentials

    • The application collects the PIN through a custom UI

    • The application securely stores PIN data using encrypted storage mechanisms

    • Independent of device biometric capabilities or system-level authentication

Use cases
  • Devices without biometric capabilities

  • Applications requiring custom authentication flows

  • Scenarios where users prefer PIN over biometric authentication

    • Cross-platform consistency requirements

Device requirements

None - works on all devices

Type name

NONE

Description

No user authentication required to access cryptographic keys

Security Level

Low

User Experience

Seamless with no authentication prompts

Behavior
  • Users can access keys immediately without any verification

  • No authentication prompts or delays

  • Cryptographic operations proceed without user interaction

  • Relies solely on device possession for security

Use cases
  • Applications with alternative security measures

Security Considerations

Anyone with device access can use the cryptographic keys

Device Requirements

None

Installing modules

Use the following module in your Android apps to bind keys to a device:

  • binding

You can optionally also install the following modules:

  • binding-ui

  • binding-migration

  • bcpkix-jdk18on (BouncyCastle open-source cryptographic library)

To install these modules into your Android app:

  1. In the Project tree view of your Android Studio project, open the build.gradle.kts file.

  2. In the dependencies section, add the binding module as a dependency:

    dependencies {
      implementation("com.pingidentity.sdk:binding:2.0.0")
    }
  3. Optionally, you can include the binding-ui dependency, which includes default UI components for the following user interactions:

    Application PIN Collection

    A Jetpack Compose dialog for secure PIN entry with:

    • Custom PIN input field with masked characters

    • Show/hide PIN visibility toggle

    • Cancel and confirm buttons

    • Error handling and validation feedback

    User Key Selection

    A default UI for multi-user scenarios that displays:

    • List of available user keys with user information

    • Selection interface when multiple users have registered devices

    • User-friendly key identification (username, creation date, etc.)

    To use the default UI dependency, add it as follows:

    dependencies {
      implementation("com.pingidentity.sdk:binding:2.0.0")
      implementation("com.pingidentity.sdk:binding-ui:2.0.0")
    }
  4. Optionally, you can include the binding-migration dependency, which helps to migrate users with binding keys created by versions of the legacy ForgeRock SDK for Android.

    The binding-migration module runs the following steps in the background and requires no additional configuration or user intervention:

    • Detects Legacy Keys: Scans for existing ForgeRock SDK device binding keys and metadata

    • Seamless Migration: Automatically migrates keys to the new SDK format during application startup

    • Preserves User Experience: Users don’t need to re-register their devices after SDK upgrade

    • One-Time Process: Migration occurs once and removes legacy data after successful migration

    • Backward Compatibility: Ensures smooth transition from Legacy SDK without data loss

    To use the binding migration dependency, add it as follows:

    dependencies {
      implementation("com.pingidentity.sdk:binding:2.0.0")
      implementation("com.pingidentity.sdk:binding-ui:2.0.0")
      implementation("com.pingidentity.sdk:binding-migration:2.0.0")
    }
  5. Optionally, if you intend to use the Application PIN authentication method to access the private keys, add the following BouncyCastle dependency:

    dependencies {
      implementation("com.pingidentity.sdk:binding:2.0.0")
      implementation("com.pingidentity.sdk:binding-ui:2.0.0")
      implementation("com.pingidentity.sdk:binding-migration:2.0.0")
      implementation("org.bouncycastle:bcpkix-jdk18on:1.82"
    }

Binding keys to a device

To bind keys to a device, the Binding Module performs the following tasks:

  1. Validation: Checks device support for authentication type

  2. Cleanup: Removes existing keys for the user

  3. Key generation: Creates new cryptographic key pair

  4. Authentication: Verifies user identity

  5. JWT Signing: Creates signed proof-of-possession

  6. Storage: Saves user key meta data

Use the deviceBindingCallback.bind() method to bind keys to the device as follows:

Binding keys to an Android device
import com.pingidentity.device.binding.journey.DeviceBindingCallback

// Simple device binding with default configuration
val result = deviceBindingCallback.bind()

result.onSuccess { jwt ->
  // Device successfully bound, JWT contains proof
  println("Device bound successfully: $jwt")
}.onFailure { error ->
  // Handle binding failure
  println("Binding failed: ${error.message}")
}

Customizing binding parameters

You can configure a number of parameters for binding, such as the device identifier, algorithm used, and the validity time:

Configuring key binding parameters on an Android device
val result = deviceBindingCallback.bind {
  // Device identification
  deviceName = "Babs' Phone"
  deviceIdentifier = DefaultDeviceIdentifier.id // Use the default device identifier strategy

  // Cryptographic settings
  signingAlgorithm = "RS256"

  // Timing configuration
  issueTime = { Instant.now() }
  expirationTime = { timeout -> Instant.now().plusSeconds(timeout.toLong()) }

  // Storage configuration
  userKeyStorage {
    storage {
      fileName = "user_keys"
    }
  }

  // Authentication configuration
  biometricAuthenticatorConfig {
    promptInfo = {
      setTitle("Device Registration")
      setSubtitle("Secure your account")
      setDescription("Use your fingerprint to register this device")
    }
  }
}

Learn about customizing the device identifier in Customizing device identifiers on Android.

Verifying bound keys on a device

To verify that a device possesses a bound key, the Binding Module performs the following tasks:

  1. Validation: Validates custom claims

  2. Key Lookup: Finds appropriate user key

  3. Authentication: Verifies user identity

  4. Challenge signing: Signs server challenge

  5. JWT creation: Creates verification JWT

Use the deviceSigningVerifierCallback.sign() method to verify possession of bound keys as follows:

Verifying key possession by signing data on an Android device
import com.pingidentity.device.binding.journey.DeviceSigningVerifierCallback

// Simple device signing
val result = deviceSigningVerifierCallback.sign()

result.onSuccess { jwt ->
  // Challenge successfully signed
  println("Challenge signed: $jwt")
}.onFailure { error ->
  // Handle signing failure
  println("Signing failed: ${error.message}")
}

Customizing signing parameters

You can configure a number of device signing parameters, such as the algorithm used, and the prompts to display:

Configuring signing parameters on an Android device
val result = deviceSigningVerifierCallback.sign {
  // Signing algorithm
  signingAlgorithm = "RS512"

  appPinConfig {
    pinRetry = 3
    pinCollector {
      "1234".toCharArray()
    }
    prompt = Prompt("App Pin", "Enter your app pin", "App pin is required")
  }

  // User key selection strategy
  userKeySelector { keys ->
    // Select most recently created key
    keys.maxByOrNull { it.createdAt } ?: keys.first()
  }

  // Authentication configuration
  biometricAuthenticatorConfig {
    promptInfo = {
      setTitle("Verify Transaction")
      setDescription("Confirm this transaction with your fingerprint")
    }
  }
}

Adding custom claims when signing using bound keys

When signing a server-provided challenge to verify possession of a bound key, you can add custom data to the resulting JSON Web Token (JWT). The server can access and use this data for context, or for auditing purposes.

Add a claims attribute to the configuration, including the key-value pairs you want to add to the JWT:

Adding custom claims to the JWT on an Android device
deviceSigningVerifierCallback.sign {
  claims {
    // Transaction details
    put("amount", "100.00")
    put("recipient", "babs@example.com")
    put("currency", "USD")

    // Device context
    put("ip_address", getClientIP())
    put("user_agent", getUserAgent())

    // Security context
    put("risk_score", calculateRiskScore())
    put("session_id", getSessionId())
  }
}

Handling errors

The Device Binding module can generate several error messages when you call bind() or sign(). Handle these errors to ensure the best possible user experience.

Common error codes and how to remediate them
Error Description Remediation

DeviceNotSupportedException

The device lacks required capabilities, or the user hasn’t enrolled.

Retry with alternative authentication requirements that don’t require biometrics.

DeviceNotRegisteredException

No keys are available for signing. Either the device hasn’t been registered, or the user has removed the authentication methods that protected the private key.

Redirect the user to bind a new key to the device.

Learn more in Handling key removal by the device.

TimeoutCancellationException

Operation exceeded timeout.

Allow retry with a longer timeout.

InvalidClaimException

Reserved claim names used in custom claims parameter.

You can’t add custom claims that match the standard required claims in a JWT, such as sub, exp, iat, and iss.

Remove or rename the claims listed in the error so they do not clash.

AbortException

The user aborted the operation.

For example the user clicked Cancel rather than provide their fingerprint.

Handle gracefully, and don’t show error.

The user chose not to continue the authentication flow.

BiometricAuthenticationException

Biometric authentication failed.

Retry biometric authentication, or offer an alternative authentication method that doesn’t require biometrics.

InvalidCredentialException

The user provided invalid credentials.

For example, the user entered an incorrect PIN number.

Allow retry and prompt for the correct credentials.

CancellationException

Coroutine operation cancelled.

Re-throw the exception to preserve cancellation semantics.

KeyPermanentlyInvalidatedException

User binds keys to their device with BiometricOnly authentication and later enrolls a new fingerprint.

User must perform device binding again to generate new keys.

Learn more in Handling biometric enrollment invalidations.

The following example shows how to handle some of these exceptions:

Handling exceptions when binding keys to a device
deviceBindingCallback.bind().fold(
  onSuccess = { jwt ->
    // Handle success
    processBindingSuccess(jwt)
  },
  onFailure = { error ->
    when (error) {
      is DeviceNotSupportedException -> {
        logger.w("Device not supported: ${error.message}")
        showFallbackAuthentication()
      }
      is TimeoutCancellationException -> {
        logger.w("Binding operation timed out")
        retryWithLongerTimeout()
      }
      else -> {
        logger.e("Binding failed", error)
        showGenericError(error.message)
      }
    }
  }
)

Handling biometric enrollment invalidations

Setting the setInvalidatedByBiometricEnrollment parameter to true when binding a new key to a device invalidates the key if the user enrolls a new fingerprint or changes the registered biometrics on the device. The Device Binding module returns KeyPermanentlyInvalidatedException in this case.

If the authentication method for signing is set to BIOMETRIC_ONLY the invalidated keys won’t be available, so the user will need to bind a new key:

Handling KeyPermanentlyInvalidatedException exceptions
// Configuration that makes keys sensitive to biometric changes
biometricAuthenticatorConfig {
    keyGenParameterSpec {
        // This setting makes keys invalid when new biometrics are enrolled
        setInvalidatedByBiometricEnrollment(true)
        //setUnlockedDeviceRequired(true)
        //setUserAuthenticationValidWhileOnBody(true)
        //setUserPresenceRequired(true)
        //setIsStrongBoxBacked(false)
        //setInvalidatedByBiometricEnrollment(false)
    }
}

// When user enrolls new fingerprint, subsequent signing will fail
deviceSigningCallback.sign().onFailure { error ->
  when (error) {
    is KeyPermanentlyInvalidatedException -> {
      // Keys are permanently invalidated due to biometric enrollment
      logger.w("Device keys invalidated by biometric enrollment")
      redirectToDeviceBinding() // User must re-bind device
    }
  }
}

Not all Android devices support the keyGenParameterSpec options for configuring how they create keys.

You should test the devices you want to support if you enable them

Handling key removal by the device

If a user disables all of the available authentication methods on their device, such as removing their fingerprints and the device PIN, Android automatically removes any keys protected by those methods from the KeyStore.

The Device Binding module returns DeviceNotRegisteredException in this case, and the user will need to bind a new key to their device:

Handling DeviceNotRegisteredException exceptions
// When all device authentication is removed, keys are deleted by Android KeyStore
deviceSigningCallback.sign().onFailure { error ->
  when (error) {
    is DeviceNotRegisteredException -> {
      logger.w("No device keys found - maybe removed due to authentication method removal")
      showMessage("Please enroll in biometrics or add a device PIN, then register your device")
      redirectToDeviceBinding() // User must re-bind device
    }
  }
}