Orchestration SDKs

Collecting device profiles in iOS

PingOne Advanced Identity Cloud PingAM iOS

The Device Profile module helps you to collect various attributes from an iOS device.

It includes preconfigured collectors to collect attributes, and allows you to create your own collectors to suit your requirements.

Step 1. Installing modules

To install the Device Profile 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 PingDeviceProfile 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.

  2. If you don’t already have a Podfile, in a terminal window, run the following command to create a new Podfile:

    pod init
  3. Add the following lines to your Podfile:

    pod 'PingDeviceProfile'
  4. Run the following command to install pods:

    pod install

Step 2. Declaring permissions

The Device Profile module respects the iOS permissions model, and certain collectors require that you declare the permissions needed.

To declare the permissions the app might request from the user:

  1. Right-click the project’s info.plist file, and select Open As > Source Code.

    Xcode displays the contents of the plist file as XML, ready for editing.

  2. Add the following properties as a child of the top-level <dict> element:

    <key>NSLocationWhenInUseUsageDescription</key>
    <string>This app needs location access to enhance security and provide personalized experiences.</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>This app needs location access to enhance security and provide personalized experiences.</string>
    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>This app uses BLE to collect device information</string>

    The LocationCollector automatically requests the relevant permission when gathering the data if needed.

  3. Save your changes.

Step 3. Collecting device profiles

To create a device profile, create a list of the collectors you want to use, or specify DefaultDeviceCollector.defaultDeviceCollectors() to use the default list, and then call the collect() method to create the device profile JSON:

Obtaining a device profile on an iOS device
import DeviceProfile

func collectDeviceProfile() async {
    // Initialize collectors with default set
    let collectors = DefaultDeviceCollector.defaultDeviceCollectors()

    // Or manually select specific collectors
    let customCollectors: [any DeviceCollector] = [
        PlatformCollector(),
        HardwareCollector(),
        NetworkCollector(),
        TelephonyCollector(),
        BrowserCollector(),
        BluetoothCollector()
        // LocationCollector() // Add separately if needed
    ]

    do {
        // Collect device information
        let deviceProfile = try await collectors.collect()

        // Use the collected profile (Dictionary)
        print("Device Profile: \(deviceProfile)")
    } catch {
        print("Collection failed: \(error)")
    }
}

The device-profile module provides the following collectors:

Included device profile collectors
Collector Attributes collected

PlatformCollector

Gathers platform and device identification information, such as the brand and model.

Example data returned by the PlatformCollector
{
   "platform": {
      "platform": "iOS",
      "version": "17.0.1",
      "device": "iPhone",
      "deviceName": "Babs' iPhone",
      "model": "iPhone15,2",
      "brand": "Apple",
      "locale": "en",
      "timeZone": "America/New_York",
      "jailBreakScore": 0.0
   }
}

HardwareCollector

Gathers information about the hardware, such as the CPU and display.

Example data returned by the HardwareCollector
{
   "hardware": {
      "manufacturer": "Apple",
      "memory": 6144,
      "cpu": 6,
      "display": {
         "width": 393,
         "height": 852,
         "orientation": 1
      },
      "camera": {
         "numberOfCameras": 3
      }
   }
}

NetworkCollector

Whether the device has any network connectivity.

Example data returned by the NetworkCollector
{
   "network": {
      "connected": true
   }
}

TelephonyCollector

Collects information about the carriers the device uses, such as the carrier name and the country code.

Example data returned by the TelephonyCollector
{
   "telephony": {
      "networkCountryIso": "US",
      "carrierName": "Verizon"
   }
}

LocationCollector

Collects latitude and longitude coordinates from the device.

  • Automatically requests permissions when needed

  • Handles both "when in use" and "always" authorization types

  • Gracefully handles permission denials

  • Includes intelligent caching, with 5-second validity

  • Returns null if a location is unavailable or permission is denied

Example data returned by the LocationCollector
{
   "location": {
      "latitude": 37.2431,
      "longitude": 115.7930
   }
}

BluetoothCollector

Whether the device has any bluetooth support.

Example data returned by the BluetoothCollector
{
    "bluetooth": {
        "supported": true
    }
}

BrowserCollector

Collects the user-agent string of the default browser on the device.

Example data returned by the BrowserCollector
{
   "browser": {
      "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15..."
   }
}

Integrating with SwiftUI

The following example demonstrates how to integrate collection of a device profile with SwiftUI:

Integrating profile collection with SwiftUI
import SwiftUI
import DeviceProfile

struct ContentView: View {
    @State private var deviceProfile: [String: Any] = [:]
    @State private var isCollecting = false

    var body: some View {
        VStack {
            if isCollecting {
                ProgressView("Collecting device information…​")
            } else {
                Button("Collect Device Profile") {
                    Task {
                        await collectProfile()
                    }
                }
            }
        }
    }

    private func collectProfile() async {
        isCollecting = true
        defer { isCollecting = false }

        let collectors = DefaultDeviceCollector.defaultDeviceCollectors()

        do {
            deviceProfile = try await collectors.collect()
        } catch {
            print("Collection error: \(error)")
        }
    }
}

Creating custom collectors

You can create custom device collectors to gather specific attributes, depending on your requirements or the hardware you will support.

Use the DeviceCollector protocol to create your custom collector:

Using DeviceCollector protocol to create a custom collector
struct BatteryCollector: DeviceCollector {
    typealias DataType = BatteryInfo

    let key = "battery"

    func collect() async throws -> BatteryInfo? {
        return BatteryInfo(
            level: UIDevice.current.batteryLevel,
            state: UIDevice.current.batteryState.rawValue
        )
    }
}

struct BatteryInfo: Codable {
    let level: Float
    let state: Int
}

Integrating with Advanced Identity Cloud and PingAM journeys

You can use the Device Profile module to collect the data the Device Profile Collector node requires when used as part of a Advanced Identity Cloud or PingAM auth journey.

The Device Profile module provides the DeviceProfileCallback class. The class includes a collect() method, which collects the data and formats it ready for return to the server.

Preparing a device profile for the Device Profile Collector node
let result = await deviceProfileCallback.collect { config in
    // Configure custom collectors for enhanced AIC risk assessment
    config.collectors {
        return [
            PlatformCollector(),
            HardwareCollector(),
            NetworkCollector(),
            BrowserCollector(),
            BluetoothCollector(),
            SecurityCollector() // Custom collector
        ]
    }
}

// Handle the result
result
    .onSuccess { profile in
        // Device profile collected, submit to AIC service
        print("Profile collected successfully")
        node.next()
    }
    .onFailure { error in
        print("Collection failed: \(error)")
    }

The module formats the resulting device profile for consumption by the Device Profile Collector node, and includes a device identifier:

Example device profile for the Device Profile Collector node
{
  "identifier": "unique-device-id",
  "metadata": {
    "platform": {
      "platform": "iOS",
      "version": "17.0.1",
      "device": "iPhone"
    },
    "hardware": {
      "manufacturer": "Apple",
      "memory": 6144,
      "cpu": 6
    },
    "network": {
      "connected": true
    }
  },
  "location": {
    "latitude": 37.2431,
    "longitude": 115.7930
  }
}

The Device Profile module automatically uses the Device ID module to generate this identifier, but you can customize how it’s generated:

Customizing the device identifier on iOS
import PingDeviceId

// Define custom device identifier config:
let identifierConfig = DeviceIdentifierConfiguration(
    keychainAccount: "com.mycompany.myapp.deviceid",
    useEncryption: true,
    keySize: 2048
)

let customDeviceIdentifier = try? DefaultDeviceIdentifier(configuration: identifierConfig)

let result = await deviceProfileCallback.collect { @Sendable config in
  // Use custom identifier in the device profile
  config.deviceIdentifier = customDeviceIdentifier
}

// Handle the result
result
  .onSuccess { profile in
      // Device profile collected, submit to server
      print("Profile collected successfully")
      node.next()
  }
  .onFailure { error in
      print("Collection failed: \(error)")
  }

Learn more about customizing the device identifier in Customizing device identifiers on iOS.