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.
-
In Xcode, in the Project Navigator, right-click your project, and then click Add Package Dependencies….
-
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. -
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.
-
Select the
PingDeviceProfilelibrary, and in the Add to Target column select the name of your project. -
Repeat the previous step for any other packages you want to use in your project.
-
Click Add Package.
Xcode displays the chosen packages and any prerequisites they might have in the Package Dependencies pane of the Project Navigator.
-
If you don’t already have CocoaPods, install the latest version.
-
If you don’t already have a Podfile, in a terminal window, run the following command to create a new Podfile:
pod init
-
Add the following lines to your Podfile:
pod 'PingDeviceProfile'
-
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:
-
Right-click the project’s
info.plistfile, and select Open As > Source Code.Xcode displays the contents of the plist file as XML, ready for editing.
-
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
LocationCollectorautomatically requests the relevant permission when gathering the data if needed. -
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:
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:
| Collector | Attributes collected |
|---|---|
|
Gathers platform and device identification information, such as the brand and model. Example data returned by the
PlatformCollector
|
|
Gathers information about the hardware, such as the CPU and display. Example data returned by the
HardwareCollector
|
|
Whether the device has any network connectivity. Example data returned by the
NetworkCollector
|
|
Collects information about the carriers the device uses, such as the carrier name and the country code. Example data returned by the
TelephonyCollector
|
|
Collects latitude and longitude coordinates from the device.
Example data returned by the
LocationCollector
|
|
Whether the device has any bluetooth support. Example data returned by the
BluetoothCollector
|
|
Collects the user-agent string of the default browser on the device. Example data returned by the
BrowserCollector
|
Integrating with SwiftUI
The following example demonstrates how to integrate collection of a device profile 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:
DeviceCollector protocol to create a custom collectorstruct 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.
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:
{
"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:
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.