Ping SDKs

Set up device profiling in iOS apps

This page shows how to detect the DeviceProfileCallback, how to collect the device profile, and how to send the profile to PingAM.

PingAM includes collected device information in its audit logs by default.

To configure PingAM to filter out this information and ensure no personally identifiable information (PII) is written to the audit logs, refer to Prevent auditing of device data.

Handle a device profile callback

If an authentication journey uses the device profile node, the SDK returns DeviceProfileCallback to collect device attributes.

You use various SDK methods to handle the callback.

Use the default device profile callback

deviceProfileCallback.execute { _ in node.next
  { (user: FRUser?, node, error) in
   self.handleNode(user: user, node: node, error: error) //Handle the node
  }

}

Customize the device profile callback

  1. The DeviceCollector protocol is a baseline class implementation protocol for the FRDeviceCollector.

    Create a new class inheriting the protocol and implement the protocol methods:

    public class CustomCollector: DeviceCollector {
          public var name: String = "customCollector"
          public func collect(completion: @escaping DeviceCollectorCallback) {
            var result: [String: Any] = [:]
            result["customID"] = "MyCustomIDValue"
            completion(result)
          }
     }
  2. Add the custom collector in the DeviceProfileCallback array of ProfileCollectors:

    deviceProfileCallback.profileCollector.collectors.append(CustomCollector())
  3. To collect the device profile and submit the node, use the DeviceProfileCallback.execute() method:

    deviceProfileCallback.execute { _ in node.next
      { (user: FRUser?, node, error) in
        self.handleNode(user: user, node: node, error: error) // Handle the node
      }
    }

If you want to remove any of the default profile collectors, access the profile collectors array of the DeviceProfileCallback node. The default list contains the following collectors:

  • PlatformCollector()

  • HardwareCollector()

  • BrowserCollector()

  • TelephonyCollector()

  • NetworkCollector()

Manually collect device profile information

Instead of responding to a device callback, your app can get the device profile using default collectors.

The FRAuth SDK provides a device profile feature that enables you to identify and obtain details about the device.

The FRDevice class includes methods for getting information about a device.

Use the FRDevice.currentDevice?.getProfile() method to return device profile:

  1. In your app, add the following code after the SDK initialization:

    FRDevice.currentDevice?.getProfile(completion: { (deviceProfile) in
        print(deviceProfile)
    })
  2. When the above code is triggered, the app prints JSON text, including the device profile, in the console.

    The device identifier is not the same as Apple’s device vendor identifier. As long as the Keychain Service configuration remains unchanged, the device identifier should remain the same for this device, even if you delete and reinstall the application.

Default collectors

Collector name Description

FRDeviceCollector

Main collector that includes other collectors and provides a collector version.

BluetoothCollector

Collect BLE support information of the device.

BrowserCollector

Collect browser information from the device; specifically, the User-Agent.

CameraCollector

Collect camera information of the device.

DisplayCollector

Collect display information of the device.

HardwareCollector

Collect hardware-related information such as the number of CPUs, number of active CPUs, and so on.

LocationCollector

Collect location information of the device.

NetworkCollector

Collect network information of the device.

PlatformCollector

Collect platform-related information, such as device jailbreak status, time zone/locale, OS version, device name, and device model.

TelephonyCollector

Collect telephony information of the device.

Sample device profile

{
    "identifier": "uiCToe3h2kdfYLOzvHpaloxsKVE=",
    "version": "1.0",
    "platform": {
        "jailbreakScore": 0.0,
        "systemInfo": {
            "sysname": "Darwin",
            "release": "18.6.0",
            "version": "Darwin Kernel Version 18.6.0: Thu Apr 25 22:14:08 PDT 2019; root:xnu-4903.262.2~2/RELEASE_ARM64_T8015",
            "nodename": "James-Go-iPhone-X",
            "machine": "iPhone10,6"
        },
        "timezone": "America/Vancouver",
        "model": "iPhone10,6",
        "version": "12.3.1",
        "locale": "en",
        "platform": "iOS",
        "device": "iPhone",
        "brand": "Apple",
        "deviceName": "James Go iPhone X"
    },
    "hardware": {
        "cpu": 6,
        "storage": 60975.11328125,
        "manufacturer": "Apple",
        "activeCPU": 6,
        "display": {
            "orientation": 1,
            "width": 375.0,
            "height": 812.0
        },
        "memory": 2823.0,
        "multitaskSupport": true,
        "camera": {
            "numberOfCameras": 4
        }
    },
    "bluetooth": {
        "supported": true
    },
    "browser": {
        "agent": "FRExample/1.0 (com.forgerock.frexample; iPhone; build:1; iOS 12.3.1) CFNetwork/978.0.7 Darwin/18.6.0 FRAuth/1.0"
    },
    "telephony": {
        "mobileCountryCode": "302",
        "voipEnabled": true,
        "isoCountryCode": "ca",
        "mobileNetworkCode": "220",
        "carrierName": "TELUS"
    },
    "network": {
        "connected": true
    },
    "location": {
        "latitute": 37.7873589,
        "longitude": -122.408227
    }
}

Modify the default collectors

You can collect the device profile using default collectors. You can also modify the default collectors.

The following code collects the device profile using the default collectors:

FRDeviceCollector.shared.collect { (result) in
    // Result is returned as [String: Any]
}

The collectors array in the FRDeviceCollector class is a public property. You can add, remove, or change any of the collectors:

@objc
  public var collectors: [DeviceCollector]

Create a custom collector

class CustomCollector: DeviceCollector {
    var name: String = "custom"

    func collect(completion: @escaping DeviceCollectorCallback) {
        var result: [String: Any] = [:]

        // Perform logic to collect any device profile
        result["key"] = "value"

        completion(result)
    }
    }
}

Device profile attributes

By default, the Ping SDK collects the following device attributes:

Attribute Value

identifier

A unique ID for the device.

To learn more about the device identifier, refer to Uniquely identifying devices.

location

The location of a device (longitude and latitude values).

This is configured in the node and requires user permissions.

metadata

Metadata for the device, including:

platform

The device OS, such as Android or iOS.

deviceName

The name of the device.

locale

The locale of the device, such as en.

timeZone

The time zone of the device, such as Africa/Johannesburg.

brand

The brand of the device, such as Apple.

jailBreakScore

A value between 0 and 1 that denotes the tampering level for a device.

Obtain user permission for the device location

Your app requires the user’s authorization to access the device location.

For information about how to request the authorization, refer to Requesting Authorization for Location Services.

Implement default jailbreak/rooted device detection

The FRJailbreakDetector class is responsible for analyzing whether the device is tampered.

The class analyzes the device by using multiple device tamper detectors and returns the highest score in the range between 0.0 to 1.0 from all the detectors.

On iOS, a device simulator always returns 1.0.

Sample using default tamper detection:

// Check if device is jailbroken
let jailbrokenScore = FRJailbreakDetector.shared.analyze()

// Evaluate the result
if jailbrokenScore == -1.0 {
    // no detectors found
}
else if jailbrokenScore == 0.0 {
    // Means that the detectors score result is 0.0
}
else {
    // Some detectors returned a possible positive result that indicates the device might be jailbroken
}

Customize jailbreak/rooted detection

The SDKs provide a set of well-known detectors. You can choose the detectors to use.

Sample custom tamper detection code:

// Remove everything and only add your selected detectors
FRJailbreakDetector.shared.detectors.removeAll()
FRJailbreakDetector.shared.detectors.append(BashDetector())
FRJailbreakDetector.shared.detectors.append(SSHDetector())

// Or loop through detectors and remove specific detector from default
for detector in FRJailbreakDetector.shared.detectors {
    // Remove specific detector if required
}

// Get result
let jailbrokenScore = FRJailbreakDetector.shared.analyze()

Implement custom detectors

Developers can implement their own detectors by extending the JailbreakDetector protocol on iOS. This protocol represents the definition of a individual analyzer for detecting if a device is rooted or jailbroken.

Each detector should analyze its logic to determine whether the device is rooted or jailbroken. Each detector returns the result score as a Double, within the range of 0.0 to 1.0.

Sample custom detector code:

// Custom detector
class CustomDetector: JailbreakDetector {
    public func analyze() -Double {
        var result = 0.0
        // do the custom logic
        return result
    }
}

// Add custom detector to JailbreakDetector
FRJailbreakDetector.shared.detectors.append(CustomDetector())

// Get the result
let jailbrokenScore = FRJailbreakDetector.shared.analyze()


Known limitations

For ForgeRock SDK for iOS v2.2.0 and earlier, the iOS SDK has discrepancies in attribute names inside the JSON payload. ForgeRock SDK for iOS v3.0.0 and later correct the attribute names to align with PingAM’s expectation and other platforms. The following attribute names were changed as of 3.0.0:

Attribute Names for 2.2.0 and earlier Attribute Names for 3.0.0 and later

timezone

timeZone

jailbreakScore

jailBreakScore

If your application still uses iOS SDK 2.2.0 or older, an adjustment in the Custom Matching Script is required in the Device Match Node to collect and analyze correct attribute values from the SDK.

More information