Integrating PingOne Recognize on iOS
PingOne Advanced Identity Cloud iOS
The PingOne Recognize module abstracts away the underlying biometric enrollment and authentication protocols, so you can add strong, passwordless authentication to your app with minimal code.
During a journey, the server returns a single callback, PingOneRecognizeCallback, and tells your app which ceremony to run using the operationType output field:
-
ENROLL: perform a biometric enrollment ceremony. -
AUTHENTICATE: perform a biometric authentication ceremony.
The Orchestration SDK reads the operationType and instantiates the matching typed callback, so the callback your app receives in the node’s callback list is not the raw PingOneRecognizeCallback, but one of:
-
PingOneRecognizeEnrollCallback: returned whenoperationTypeisENROLL; callenroll()on it. -
PingOneRecognizeAuthenticateCallback: returned whenoperationTypeisAUTHENTICATE; callauthenticate()on it.
Your app never configures the underlying mobile SDK itself. All configuration, such as the service host and API key, arrives in the callback output from the node.
Before you start, make sure your app meets the following requirements:
-
The app declares camera usage in its
Info.plistwith theNSCameraUsageDescriptionkey; the biometric ceremony needs camera access to capture the user’s face. -
The app runs the ceremony on the main thread with an active view controller in the hierarchy, so the biometric UI can present over your app.
Also, ensure that you have completed the server-side setup for PingOne Recognize.
Step 1. Preparing the project to download the dependency
The PingRecognize module depends on the PingOne Recognize mobile SDK, which Ping distributes from a private Cloudsmith repository. The PingRecognize module itself is published to the Orchestration SDK for iOS repository, but your package manager cannot resolve its dependency tree, and therefore cannot download the module, until you configure access to that repository.
To configure your project to download the SDK, obtain an API key and add the repository:
-
When your PingOne Recognize tenant has been provisioned, sign in to the dashboard for your region to obtain an API key for the
keylessrepositories: -
Add the secure repository so your project can download the underlying Keyless module.
Your package manager downloads the PingOne Recognize mobile SDK from this protected repository when it resolves the
PingRecognizedependency you add in Step 2. Installing the module.Complete only the option for your chosen package manager:
- CocoaPods
-
-
If you are using CocoaPods, add the Keyless spec repository to your
Podfilewhen you create it in Step 2. Installing the module, replacing <your-api-key> with the API key you obtained in the previous step:source 'https://dl.cloudsmith.io/<your-api-key>/keyless/partners/cocoapods/index.git'Never commit API keys to source control.
Inject the key into your
Podfilefrom an environment variable, or generate the file in your CI pipeline.
-
- Swift Package Manager
-
-
If you are using the Swift Package Manager, with Xcode closed, in a terminal window run the following commands to map the registry to the
keylessscope:swift package-registry set --global --scope keyless https://swift.cloudsmith.io/keyless/partners/ -
Run the following to authenticate with the API key you obtained in the previous step:
swift package-registry login https://swift.cloudsmith.io/keyless/partners/ --token <your-api-key>Never commit API keys to source control.
The login command stores the key in your macOS keychain, so it is never written to any project file.
-
Step 2. Installing the module
Add the PingRecognize module to your Orchestration SDK project.
You can use CocoaPods or the Swift Package Manager to add the dependencies to your iOS project.
Add the dependency using CocoaPods
-
If you do not already have a
Podfile, in a terminal window, run the following command in your project directory to create one:pod init -
Add the following lines to your
Podfile:pod 'PingJourney' pod 'PingRecognize' // Add-on for PingOne Recognize -
If you configured the CocoaPods option in Step 1, run the following command to install the pods:
pod install
Add the dependency using Swift Package Manager
-
With your project open in Xcode, select File > Add Package Dependencies.
-
In the search bar, enter the Orchestration SDK for iOS repository URL:
https://github.com/ForgeRock/ping-ios-sdk. -
Select the
ping-ios-sdkpackage, and then click Add Package. -
In the Choose Package Products modal, ensure that the
PingRecognizelibrary is added to your target project. -
Click Add Package.
|
If the |
-
Import the module in the files where you handle the callback:
// Import the Journeys library import PingJourney // Import the PingOne Recognize library import PingRecognize
Step 3. Handling the enroll callback
The server triggers an enrollment ceremony by returning a PingOneRecognizeCallback with operationType = "ENROLL".
The Orchestration SDK resolves the callback from the output fields and instantiates PingOneRecognizeEnrollCallback, so your app only needs to identify that callback in the node’s callback list and call enroll() on it:
import PingJourney
import PingRecognize
if let continueNode = node as? ContinueNode {
for callback in continueNode.callbacks {
if let callback = callback as? PingOneRecognizeEnrollCallback {
let result = await callback.enroll()
switch result {
case .success:
// Enrollment successful, proceed to the next step in the journey
await continueNode.next()
case .failure(let error):
// Enrollment failed, handle the error; see Error handling
print("Enrollment failed: code=\(error.code), message=\(error.message)")
await continueNode.next()
}
}
}
}
Optionally, set the retrieveSelfie option to true to receive the image captured during the ceremony on the result:
let result = await callback.enroll { config in
config.retrieveSelfie = true
}
switch result {
case .success(let enrollSuccess):
let selfie: CGImage? = enrollSuccess.selfie
// Enrollment successful, proceed to the next step in the journey
await continueNode.next()
case .failure(let error):
// Enrollment failed, handle the error
print("Enrollment failed: code=\(error.code), message=\(error.message)")
await continueNode.next()
}
Step 4. Handling the authenticate callback
The server requests a biometric authentication ceremony by returning a PingOneRecognizeCallback with operationType = "AUTHENTICATE".
The Orchestration SDK resolves the callback from the output fields and instantiates PingOneRecognizeAuthenticateCallback, so to authenticate an enrolled user, your app only needs to identify that callback in the node’s callback list and call authenticate() on it:
import PingJourney
import PingRecognize
if let continueNode = node as? ContinueNode {
for callback in continueNode.callbacks {
if let callback = callback as? PingOneRecognizeAuthenticateCallback {
let result = await callback.authenticate()
switch result {
case .success:
// Authentication successful, proceed to the next step in the journey
await continueNode.next()
case .failure(let error):
// Authentication failed, handle the error; see Error handling
print("Authentication failed: code=\(error.code), message=\(error.message)")
await continueNode.next()
}
}
}
}
The callbacks submit their inputs to the journey automatically. Do not call input() manually, or you risk submitting a duplicate result.
On success, the SDK submits the following inputs to the server automatically:
- Signed JWT
-
A JWT signed by the Keyless authentication service attesting that the ceremony completed on the device: the Keyless service issued it after the user’s captured face matched their enrolled biometric template and passed the liveness checks. The Keyless authentication service, not your app, signs the JWT, so the server can verify its signature rather than trust a result claimed by the client. If the node configured transaction data, the signed JWT also carries it, which binds the ceremony to that specific transaction.
- Current user ID
-
The user’s PingOne Recognize identifier.
- Client state
-
An opaque blob used to restore the user’s account on a new device.
- Device public signing key
-
The public part of the key pair the device generated during enrollment.
|
If the server sends a value in the The Orchestration SDK passes the client state to the PingOne Recognize mobile SDK, which uses the client state to restore the user’s account on the device. The Orchestration SDK handles this fallback itself, so no extra code is needed in your app. |
Error handling
All Recognize SDK failures are wrapped in a RecognizeError before they reach your code:
struct RecognizeError: Error, LocalizedError, Sendable {
let message: String // Human-readable description
let code: Int // Numeric code from the underlying Keyless SDK
let debuggingInfo: [String: String] // Diagnostic entries, such as the ceremony flow ID, session ID, and stack trace
}
The code and message values are injected into the clientError and clientErrorCode input slots. You must call continueNode.next() after encountering an error so your server receives the error detail, regardless of how your app handles the failed result.
The failure case of the result carries the RecognizeError as its error value, so you can read its code directly, for example:
import PingRecognize
let result = await callback.authenticate()
switch result {
case .success:
// Authentication successful, proceed to the next step in the journey
await continueNode.next()
case .failure(let error):
if let error = error as? RecognizeError {
switch error.code {
case 30003: showRetryPrompt("Face scan cancelled") // User cancelled
case 30004: showRetryPrompt("Face did not match") // Face not matching
case 30005: showRetryPrompt("No network connection") // No network connection
case 30007: showLockoutScreen() // User lockout
case 30009: requestCameraPermission() // Camera permission denied
default: logUnexpectedError(error)
}
}
// Continue the journey to report client errors back to the server
await continueNode.next()
}
The codes in the example are in the user error range of 30000 and above, which result from user behavior during the ceremony.
Codes below 30000 indicate integration or internal errors, such as 20000 when the user is not yet enrolled on the device.