---
title: Binding keys to a device in iOS
description: PingOne Advanced Identity Cloud PingAM iOS
component: orchsdks
page_id: orchsdks:journey:use-cases/device-binding/ios-device-binding
canonical_url: https://developer.pingidentity.com/orchsdks/journey/use-cases/device-binding/ios-device-binding.html
revdate: Tue, 29 Oct 2025 14:22:33 +0100
keywords: ["Device", "Hardware", "Source Code", "Integration", "SDK", "iOS"]
section_ids:
  before_you_begin: Before you begin
  securing_access_to_the_keys: Securing access to the keys
  ios_device-binding_module: Installing modules
  binding_keys_to_a_device: Binding keys to a device
  verifying_bound_keys_on_a_device: Verifying bound keys on a device
  adding_custom_claims_when_signing_using_bound_keys: Adding custom claims when signing using bound keys
  configuring_authenticator_parameters: Configuring authenticator parameters
  replacing_the_default_system_ui: Replacing the default system UI
  creating_a_custom_ui_for_pin_entry: Creating a custom UI for PIN entry
  pin_ui_view: Step 1. Create a Custom UI for PIN Collection
  pin_ui_protocol: Step 2. Implement the PinCollector Protocol
  pin_ui_configure: Step 3. Use the custom collector for binding and signing
  creating_a_custom_ui_for_key_selection: Creating a custom UI for key selection
  key_ui_view: Step 1. Create a custom UI for key selection
  key_ui_protocol: Step 2. Implement the UserKeySelector Protocol
  key_ui_configure: Step 3. Use the custom key selector for signing
  handling_errors: Handling errors
---

# Binding keys to a device in iOS

[icon: circle-check, set=far]PingOne Advanced Identity Cloud [icon: circle-check, set=far]PingAM [icon: apple, set=fab]iOS

The Device Binding module provides secure device registration and authentication capabilities for iOS 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](https://docs.pingidentity.com/auth-node-ref/latest/device-binding.html)

  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](https://docs.pingidentity.com/auth-node-ref/latest/device-signing-verifier.html)

  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](https://docs.pingidentity.com/auth-node-ref/latest/device-binding-storage.html)

  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

To install the Device Binding module for iOS, use Swift Package Manager (SPM) or Cocoapods to add the dependency to your project.

* SPM (Swift Package Manager)

* CocoaPods

You can install packages by using SPM (Swift Package Manager) on the iOS project.

1. In Xcode, in the Project Navigator, right-click your project, and then click Add Package Dependencies…​.

2. In the **Search or Enter Package URL** field, enter the URL of the repo containing the Orchestration SDK for iOS, `https://github.com/ForgeRock/ping-ios-sdk.git`.

3. In **Add to Project**, select the name of your project, and then click **Add Package**.

   Xcode shows a dialog containing the libraries available for iOS.

4. Select the `PingBinding` library, and in the **Add to Target** column select the name of your project.

5. Repeat the previous step for any other packages you want to use in your project.

6. Click **Add Package**.

   Xcode displays the chosen packages and any prerequisites they might have in the **Package Dependencies** pane of the Project Navigator.

1) If you don't already have CocoaPods, install the [latest version](https://guides.cocoapods.org/using/getting-started.html).

2) If you don't already have a Podfile, in a terminal window, run the following command to create a new [Podfile](https://guides.cocoapods.org/syntax/podfile.html):

   ```
   pod init
   ```

3) Add the following lines to your Podfile:

   ```
   pod 'PingBinding'
   ```

4) Run the following command to install pods:

   ```
   pod install
   ```

## 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 `bind()` method to bind keys to the device as follows:

Binding keys to an iOS device

```swift
import PingBinding
import PingJourney

private func handleDeviceBinding() {
  Task {
    let result = await callback.bind()
    switch result {
      case .success(let json):
        print("Device binding success: \(json)")
      case .failure(let error):
        if let deviceBindingStatus = error as? DeviceBindingStatus {
          print("Device binding failed: \(deviceBindingStatus.errorMessage)")
        } else {
          print("Device binding failed: \(error.localizedDescription)")
        }
    }
    onNext()
  }
}
```

## 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 iOS device

```swift
import PingBinding
import PingJourney

func handleDeviceSigning(callback: DeviceSigningVerifierCallback, onNext: @escaping () -> Void) {
    Task {
        let result = await callback.sign()
        switch result {
        case .success:
            print("Signing successful")
        case .failure(let error):
            print("Signing failed: \(error.localizedDescription)")
        }
        // Continue to the next node
        onNext()
    }
}
```

### 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 iOS device

```swift
let result = await callback.sign { config in
  // Use custom claims
  config.claims = [
    "amount": "100.00",
    "recipient": "babs@example.com",
    "currency": "USD",
  ]
}
```

## Configuring authenticator parameters

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

Configuring AppPinAuthenticator parameters on iOS

```swift
import PingBinding

let result = await callback.bind { config in
    // Specify custom app PIN auth parameters
    let appPinConfig = AppPinConfig(
        logger: myCustomLogger,
        prompt: Prompt(title: "Enter PIN", subtitle: "Security", description: "Enter your 4-digit PIN"),
        pinRetry: 5,
        keyTag: "my-custom-key-tag",
        pinCollector: CustomPinCollector()
    )

    // Use the custom authenticator with the config
    config.deviceAuthenticator = AppPinAuthenticator(config: appPinConfig)
}
```

Configuring Biometric authenticator parameters on iOS

```swift
import PingBinding

let result = await callback.bind { config in
    // Specify custom biometric auth parameters
    let biometricConfig = BiometricAuthenticatorConfig(
        logger: myCustomLogger,
        keyTag: "my-biometric-key-tag",
    )

    // Set the authenticator config - uses the appropriate authenticator (BiometricOnlyAuthenticator
    // or BiometricDeviceCredentialAuthenticator) based on the callback type
    config.authenticatorConfig = biometricConfig
}
```

## Replacing the default system UI

The **PingBinding** module uses system dialogs and alerts by default in the following situations:

* Application PIN entry

  If the authentication journey specifies the `APPLICATION_PIN` authentication method to access the secure keys, the module uses a system alert to obtain the PIN from the user.

* Key selection

  If there are multiple keys available on the device that could be used to fulfil the request, the module uses `UIAlertController` with an action sheet to display the suitable keys to the user to choose the correct one.

You can implement your own custom user interface for both of these situations.

### Creating a custom UI for PIN entry

To implement a custom UI for accepting PIN codes from the user, complete the following steps:

1. [Step 1. Create a Custom UI for PIN Collection](#pin_ui_view)

2. [Step 2. Implement the PinCollector Protocol](#pin_ui_protocol)

3. [Step 3. Use the custom collector for binding and signing](#pin_ui_configure)

#### Step 1. Create a Custom UI for PIN Collection

You need to create a view that serves as your PIN entry screen.

The following example uses SwiftUI to create a simple view that collects a 4-digit PIN.

Creating a SwiftUI view to collect a 4-digit PIN

```swift
// In your application, e.g., PinCollectorView.swift
import SwiftUI
import PingBinding

struct PinCollectorView: View {
    let prompt: Prompt
    let completion: (String?) -> Void

    @State private var pin: String = ""

    var body: some View {
        VStack(spacing: 20) {
            Text(prompt.title)
                .font(.title)
            Text(prompt.description)
                .font(.subheadline)

            TextField("4-digit PIN", text: $pin)
                .keyboardType(.numberPad)
                .padding()

            HStack {
                Button("Cancel") { completion(nil) }
                Button("Submit") { completion(pin) }
                    .disabled(pin.count != 4)
            }
        }
        .padding()
    }
}
```

#### Step 2. Implement the PinCollector Protocol

To allow the **PingBinding** module to present your custom UI and return the collected PIN code, implement the `PinCollector` protocol.

Implementing the PinCollector protocol

```swift
// In your application, e.g., CustomPinCollector.swift
import UIKit
import SwiftUI
import PingBinding

class CustomPinCollector: PinCollector {
    func collectPin(prompt: Prompt, completion: @escaping @Sendable (String?) -> Void) {
        DispatchQueue.main.async {
            guard let topVC = UIApplication.shared.windows.first?.rootViewController else {
                completion(nil)
                return
            }

            let pinView = PinCollectorView(prompt: prompt) { pin in
                topVC.dismiss(animated: true) {
                    completion(pin)
                }
            }

            let hostingController = UIHostingController(rootView: pinView)
            topVC.present(hostingController, animated: true)
        }
    }
}
```

#### Step 3. Use the custom collector for binding and signing

To use your new UI for binding and signing, specify the implementation class in the configuration of the `sign()` and `bind()` methods.

Configure binding to use a custom UI for collecting app PINs

```swift
// In your view that handles the DeviceBindingCallback
import PingBinding

func handleDeviceBinding(callback: DeviceBindingCallback, onNext: @escaping () -> Void) {
    Task {
        let result = await callback.bind { config in
            // Customize the PIN collector for application PIN authentication
            config.pinCollector = CustomPinCollector()
        }

        // Handle result...
        onNext()
    }
}
```

Configure signing to use a custom UI for collecting app PINs

```swift
// In your view that handles the DeviceSigningVerifierCallback
import PingBinding

func handleDeviceSigning(callback: DeviceSigningVerifierCallback, onNext: @escaping () -> Void) {
    Task {
        let result = await callback.sign { config in
            // Customize the PIN collector for application PIN authentication
            config.pinCollector = CustomPinCollector()
        }

        // Handle result...
        onNext()
    }
}
```

### Creating a custom UI for key selection

Implementing a custom UI for displaying available key pairs to the user to select the correct one is useful in the following situations:

* Multiple users have bound keys on the same physical device

* You want to provide a branded UI for key selection

* You need to display additional context about each key to help with selection

To implement a custom UI for displaying available key pairs, complete the following steps:

. . .

#### Step 1. Create a custom UI for key selection

You need to create a view that displays the available keys and allow the user to select one.

The following example uses SwiftUI to create a view that allows key selection:

Creating a SwiftUI view to allow key selection

```swift
// In your application, e.g., UserKeySelectorView.swift
import SwiftUI
import PingBinding

struct UserKeySelectorView: View {
    let userKeys: [UserKey]
    let prompt: Prompt
    let completion: (UserKey?) -> Void

    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                if !prompt.description.isEmpty {
                    Text(prompt.description)
                        .font(.body)
                        .multilineTextAlignment(.center)
                        .padding()
                }

                List(userKeys, id: \.id) { userKey in
                    Button(action: {
                        completion(userKey)
                    }) {
                        VStack(alignment: .leading, spacing: 4) {
                            if !userKey.username.isEmpty {
                                Text(userKey.username)
                                    .font(.headline)
                            }
                            if !userKey.userId.isEmpty {
                                Text("User ID: \(userKey.userId)")
                                    .font(.caption)
                                    .foregroundColor(.secondary)
                            }
                            Text("Auth: \(userKey.authType.rawValue)")
                                .font(.caption)
                                .foregroundColor(.secondary)
                        }
                        .padding(.vertical, 4)
                    }
                }
            }
            .navigationTitle(prompt.title.isEmpty ? "Select Device Key" : prompt.title)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") {
                        completion(nil)
                    }
                }
            }
        }
    }
}
```

#### Step 2. Implement the UserKeySelector Protocol

To allow the **PingBinding** module to present your custom UI and return the selected key, implement the `UserKeySelector` protocol.

Implementing the UserKeySelector protocol

```swift
// In your application, e.g., CustomUserKeySelector.swift
import UIKit
import SwiftUI
import PingBinding

class CustomUserKeySelector: UserKeySelector {
    func selectKey(userKeys: [UserKey], prompt: Prompt) async -> UserKey? {
        return await withCheckedContinuation { continuation in
            DispatchQueue.main.async {
                guard let topVC = self.getTopViewController() else {
                    continuation.resume(returning: nil)
                    return
                }

                let selectorView = UserKeySelectorView(
                    userKeys: userKeys,
                    prompt: prompt
                ) { selectedKey in
                    topVC.dismiss(animated: true) {
                        continuation.resume(returning: selectedKey)
                    }
                }

                let hostingController = UIHostingController(rootView: selectorView)
                hostingController.modalPresentationStyle = .formSheet
                topVC.present(hostingController, animated: true)
            }
        }
    }

    private func getTopViewController() -> UIViewController? {
        guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
              let window = windowScene.windows.first(where: { $0.isKeyWindow }),
              let rootViewController = window.rootViewController else {
            return nil
        }

        var topViewController = rootViewController
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }

        return topViewController
    }
}
```

#### Step 3. Use the custom key selector for signing

To use your new UI signing, specify the implementation class in the configuration of the `sign()` and `bind()` methods.

Configure binding to use a custom UI for selecting keys

```swift
import PingBinding

func handleDeviceSigning(callback: DeviceSigningVerifierCallback, onNext: @escaping () -> Void) {
    Task {
        let result = await callback.sign { config in
            // Use custom UI for selecting from multiple device keys
            config.userKeySelector = CustomUserKeySelector()
        }

        switch result {
        case .success:
            print("Signing successful")
        case .failure(let error):
            print("Signing failed: \(error.localizedDescription)")
        }

        onNext()
    }
}
```

## Handling errors

If the Device Binding module returns `failure` when you call `bind()` or `sign()`, you can get the details of the error and take the appropriate action.

**Common error codes and how to remediate them**

| Error                 | Description                                                                                                                                                                | Remediation                                                                                     |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `unsupported`         | The device lacks required capabilities, or the user hasn't enrolled.                                                                                                       | Retry with alternative authentication requirements that don't require biometrics.               |
| `clientNotRegistered` | 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.                                              |
| `timeout`             | Operation exceeded timeout.                                                                                                                                                | Allow retry with a longer timeout.                                                              |
| `invalidCustomClaims` | 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.                           |
| `abort`               | 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. |
| `unAuthorize`         | The user provided invalid credentials.For example, the user entered an incorrect PIN number.                                                                               | Allow retry and prompt for the correct credentials.                                             |

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

Handling exceptions when binding keys to a device

```swift
let result = await callback.sign()
switch result {
  case .success(let json):
    print("Device signing success: \(json)")
  case .failure(let error):
    if let deviceBindingStatus = error as? DeviceBindingStatus {
      print("Device signing failed: \(deviceBindingStatus.errorMessage)")
    } else {
      print("Device signing failed: \(error.localizedDescription)")
    }
}
onNext()
```
