Device self-service in iOS apps
PingOne Advanced Identity Cloud PingAM iOS
The Device Client module for iOS provides a comprehensive and unified API for managing Multi-Factor Authentication (MFA) devices and user profile devices registered with Advanced Identity Cloud or PingAM servers.
The module simplifies the process of retrieving, updating, and deleting various types of authentication devices, enabling you to build secure and user-friendly device management experiences within your iOS applications.
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.
-
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
PingDeviceClientlibrary, 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 'PingDeviceClient', '~> 2.0.0' -
Run the following command to install pods:
pod install
Initializing the device client
The Device Client module uses the REST-based device management endpoints provided by Advanced Identity Cloud and PingAM.
The Device Client module requires the session token when making calls to the device management endpoints.
Session tokens often have a short duration and might expire after 5 minutes. If the client does not have an active session token you should trigger an authentication journey to obtain a new session token before attempting to manage registered devices.
To initialize the Device Client module, create a configuration object that includes the user’s session token, and pass it to the DeviceClient() method:
import PingDeviceClient
let journey = Journey.createJourney { config in
config.serverUrl = "https://openam-forgerock-sdks.forgeblocks.com/am"
config.realm = "alpha"
config.cookie = "ch15fefc5407912"
}
// Authenticate the user
let node = journey.start("sdkUsernamePasswordJourney")
// ... Step through the journey nodes until success...
switch node {
case let successNode as SuccessNode:
// Authentication successful!
// Retrieve the session
let session = successNode.session
let ssoToken = session.value // Extract the SSO token
// Configure DeviceClient with the SSO token
let config = DeviceClientConfig(
serverUrl: "https://openam-forgerock-sdks.forgeblocks.com/am",
realm: "alpha"
cookieName: "ch15fefc5407912"
ssoToken: ssoToken
)
// Initialize device client
let deviceClient = DeviceClient(config: config)
}
The configuration properties for the device client are as follows:
| Parameter | Description | Required? |
|---|---|---|
|
The session, or SSO token, obtained after successful authentication. |
Yes |
|
The base URL of your server.
|
Yes |
|
The authentication realm.
|
Yes |
|
The session cookie name.
|
Yes |
Listing registered devices
For each type of registered device you can call the get() method to retrieve a list from the server.
The available types of device are as follows:
| Registered device type | Listing method |
|---|---|
WebAuthn / FIDO authenticators |
|
Bound devices |
|
Profiled devices |
|
Push MFA devices |
|
OATH MFA devices |
|
The different device types return different relevant information that you can access and display to the user as appropriate, such as the current name or when the device was last used.
The following code shows how to obtain lists of the different devices from the server:
-
WebAuthn / FIDO
-
Bound devices
-
Profiled devices
-
Push devices
-
OATH devices
let result = await deviceClient.webAuthn.get()
switch result {
case .success(let devices):
for device in devices {
print("ID: \(device.id)")
print(" Device Name: \(device.deviceName)")
print(" Credential ID: \(device.credentialId)")
print(" UUID: \(device.uuid)")
print(" Created: \(Date(timeIntervalSince1970: device.createdDate))")
print(" Last Access: \(Date(timeIntervalSince1970: device.lastAccessDate))")
print(" URL Suffix: \(device.urlSuffix)")
print("----")
}
case .failure(let error):
handleError(error)
}
let result = await deviceClient.bound.get()
switch result {
case .success(let devices):
for device in devices {
print("ID: \(device.id)")
print(" Device Name: \(device.deviceName)")
print(" Device ID: \(device.deviceId)")
print(" UUID: \(device.uuid)")
print(" Created: \(Date(timeIntervalSince1970: device.createdDate))")
print(" Last Access: \(Date(timeIntervalSince1970: device.lastAccessDate))")
print(" URL Suffix: \(device.urlSuffix)")
print("----")
}
case .failure(let error):
handleError(error)
}
let result = await deviceClient.profile.get()
switch result {
case .success(let devices):
for device in devices {
print("ID: \(device.id)")
print(" Device Name: \(device.deviceName)")
print(" Identifier: \(device.identifier)")
print(" Platform: \(device.metadata["platform"] as? String ?? "Unknown")")
if let location = device.location {
print(" Location: \(location.latitude), \(location.longitude)")
}
print(" Last Selected: \(Date(timeIntervalSince1970: device.lastSelectedDate))")
print(" URL Suffix: \(device.urlSuffix)")
print("----")
}
case .failure(let error):
handleError(error)
}
let result = await deviceClient.push.get()
switch result {
case .success(let devices):
for device in devices {
print("ID: \(device.id)")
print(" Device Name: \(device.deviceName)")
print(" UUID: \(device.uuid)")
print(" Created: \(Date(timeIntervalSince1970: device.createdDate))")
print(" Last Access: \(Date(timeIntervalSince1970: device.lastAccessDate))")
print(" URL Suffix: \(device.urlSuffix)")
print("----")
}
case .failure(let error):
handleError(error)
}
let result = await deviceClient.oath.get()
switch result {
case .success(let devices):
for device in devices {
print("ID: \(device.id)")
print(" Device Name: \(device.deviceName)")
print(" UUID: \(device.uuid)")
print(" Created: \(Date(timeIntervalSince1970: device.createdDate))")
print(" Last Access: \(Date(timeIntervalSince1970: device.lastAccessDate))")
print(" URL Suffix: \(device.urlSuffix)")
print("----")
}
case .failure(let error):
handleError(error)
}
Renaming devices
You can rename registered devices and update the user’s account with the new details by using the update() method.
For example, the following code renames a registered bound device:
let fetchResult = await deviceClient.bound.get()
if case .success(var devices) = fetchResult,
var device = devices.first {
device.deviceName = "My iPhone 17 Pro"
let updateResult = await deviceClient.bound.update(device)
switch updateResult {
case .success:
print("Device updated successfully")
case .failure(let error):
print("Update failed: \(error.localizedDescription)")
}
}
|
All update operations automatically include an This header is used for optimistic concurrency control to prevent conflicts when multiple clients attempt to modify the same device simultaneously. The wildcard |
Deleting devices
You can delete or deregister a device, with the caveat that the authentication journey that provided the users' session must fulfil one or more of the following criteria:
-
Used same multi-factor authentication method as the device you want to delete.
For example, to delete a WebAuthn device the authentication journey that created the session must also authenticate using a WebAuthn device.
-
Used the Enable Device Management node that alters the Device Check Enforcement Strategy.
Use the delete() method to delete a device from the user’s profile:
let fetchResult = await deviceClient.push.get()
if case .success(let devices) = fetchResult,
let device = devices.first {
let deleteResult = await deviceClient.push.delete(device)
switch deleteResult {
case .success:
print("Device deleted")
case .failure(let error):
print("Delete failed: \(error)")
}
}