Integrating PingOne Recognize in JavaScript
PingOne Advanced Identity Cloud JavaScript
The PingOne Recognize package abstracts away the underlying biometric enrollment and authentication protocol, so you can add strong, passwordless authentication to your web 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 @forgerock/recognize package renders the ceremony as a web component in your page. The component handles the camera stream, guides the user through the ceremony, and creates the biometric template in the browser. Your app creates a PingOne Recognize client from the callback outputs, mounts the component, observes its events, and submits the outcome to the journey through the callback.
Your app never configures the underlying PingOne Recognize web SDK itself. All configuration, such as the service URL, the customer name, and the username, arrives in the callback output from the node.
Before you start, make sure your app meets the following requirements:
-
The page is served over HTTPS, so the browser grants the ceremony access to the camera.
-
The device has a camera the browser can access; the ceremony captures the user’s face.
Also, ensure that you have completed the server-side setup for PingOne Recognize.
Step 1. Installing the module
Add the @forgerock/recognize package to your Orchestration SDK project.
The package embeds the underlying PingOne Recognize web SDK, so no additional repository configuration is needed to install it.
Install the package with npm:
npm install @forgerock/recognize
The Recognize callback is delivered by the @forgerock/journey-client package, which your app already uses to run journeys.
Step 2. Handling the Recognize callback
The server tells your app which ceremony to run through the callback’s operationType output. It returns a PingOneRecognizeCallback with operationType = "ENROLL" to trigger an enrollment ceremony, and with operationType = "AUTHENTICATE" to request a biometric authentication ceremony.
Your app handles both ceremonies with the same sequence of calls: find the callback in the step’s callback list, create a PingOne Recognize client from the callback outputs, mount the ceremony component into an element in your page, and set the results on the callback when the ceremony completes.
The following example code reads the operationType output and maps it to the ceremony type the client expects, so the same code runs either ceremony:
import { callbackType, PingOneRecognizeCallback } from '@forgerock/journey-client';
import { recognize } from '@forgerock/recognize';
import type { RecognizeWebComponentConfiguration } from '@forgerock/recognize/types';
// Find the Recognize callback in the step’s callback list
const recognizeCallback = step.callbacks.find(
(cb) => cb.getType() === callbackType.PingOneRecognizeCallback,
) as PingOneRecognizeCallback; (1)
// Map the operation type the server set to the ceremony type the client expects
const ceremonyType = recognizeCallback.getOperationType() === 'ENROLL' ? 'enroll' : 'auth'; (2)
// Assemble the ceremony configuration from the callback outputs,
// including any extra options the journey node passes to the Recognize web SDKs
const config: RecognizeWebComponentConfiguration = {
customer: recognizeCallback.getCustomerName(),
serviceURL: recognizeCallback.getServiceURL(),
...recognizeCallback.getOptions(),
};
if (recognizeCallback.getTransactionData()) {
config.transactionData = recognizeCallback.getTransactionData();
}
const client = recognize(config); (3)
// Subscribe to the ceremony events, then start the ceremony
await new Promise((resolve, reject) => {
client.subscribe({ (4)
next: (event) => {
// Ceremony progress, such as step changes
},
error: (err) => {
// The ceremony failed; submit the error detail to the journey
recognizeCallback.setClientError(err.error.message);
recognizeCallback.setClientErrorCode(String(err.error.code));
resolve();
},
complete: (data) => {
// The ceremony succeeded; submit the signed JWT and Recognize ID
if (data.jwt) {
recognizeCallback.setSignedJwt(data.jwt);
recognizeCallback.setRecognizeId(JSON.parse(atob(data.jwt.split('.')[1])).sub);
}
resolve();
},
});
client
.init({ (5)
mode: 'mount',
container: document.getElementById('recognize'),
type: ceremonyType,
username: recognizeCallback.getUsername(),
})
.then((initError) => {
if (initError) {
recognizeCallback.setClientError(initError.error.message);
recognizeCallback.setClientErrorCode(String(initError.error.code));
}
resolve();
});
});
// Remove the ceremony component
client.dispose(); (6)
// Continue the journey
step = await journeyClient.next(step); (7)
1 The PingOneRecognizeCallback arrives in the step’s callback list like any other callback. The code above finds it by its callback type, and casts it to the PingOneRecognizeCallback class from the @forgerock/journey-client package, which provides the Recognize methods the snippet calls.
2 The server tells your app which ceremony to run through the callback’s operationType output: "ENROLL" for enrollment, "AUTHENTICATE" for authentication. Read it with getOperationType(), and map the value to the ceremony type the recognize client expects: enroll for enrollment, auth for authentication.
3 Create the Recognize client with recognize(), passing it the ceremony configuration assembled from the callback outputs:
-
customer: the customer name registered on the Recognize server, read from the callback withgetCustomerName(). -
serviceURL: the authentication service URL of the Recognize server, read from the callback withgetServiceURL(). -
the transaction data, when the node configured it, read from the callback with
getTransactionData(). -
any extra options the journey node passes to the Recognize web SDKs, read from the callback with
getOptions().
4 Subscribe an observer to the client with subscribe() before starting the ceremony. The observer receives the ceremony outcome:
-
complete: the ceremony succeeded. Set the values the server needs to verify the ceremony result on the callback:-
The signed JWT from the data, with
setSignedJwt(). The Keyless authentication service issued it after the user’s captured face matched their enrolled biometric template and passed the liveness checks, attesting that the ceremony completed on the device. 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. -
the user’s PingOne Recognize identifier, with
setRecognizeId(), read from thesubclaim of the signed JWT.
-
-
error: the ceremony failed. Set the error message and code on the callback withsetClientError()andsetClientErrorCode()so the journey can continue on its Client Error outcome.
5 Start the ceremony with init(), passing it the element in your page to mount the ceremony component in, the ceremony type you derived in the previous step, and the username read from the callback with getUsername(). The component handles the camera stream and guides the user through capturing their face.
6 When the ceremony finishes, remove the component with dispose().
7 Continue the journey by passing the step to journeyClient.next(). The callback submits the values you set on it to the server, and the journey continues on the outcome matching the ceremony result.
Error handling
The recognize client reports every failure through the observer’s error callback as a RecognizeError object:
{
error: {
code: 2002, // Numeric code from the Recognize error code set
message: 'CAMERA_PERMISSION_DENIED', // Name of the error in the code set
cause: originalError, // The underlying error, when present
},
}
Call the callback’s setClientError() and setClientErrorCode() methods with the error’s message and code, so your server receives the error detail regardless of how your app handles the failure.
If the ceremony cannot start, init() resolves with a RecognizeError instead of reporting it through the observer, for example when the browser cannot load the underlying WebAssembly module.
For example, to handle the errors users most commonly encounter during a ceremony:
switch (err.error.code) {
case 1005: // User cancelled
showRetryPrompt('Face scan cancelled');
break;
case 3004: // Face not matching
showRetryPrompt('Face did not match');
break;
case 1007: // No network connection
showRetryPrompt('No network connection');
break;
case 3007: // User lockout
showLockoutScreen();
break;
case 2002: // Camera permission denied
requestCameraPermission();
break;
default:
logUnexpectedError(err);
}
// Continue the journey to report client errors back to the server
step = await journeyClient.next(step);
The error codes group by source: integration errors in the 1000 range, such as 1005 when the user cancels the ceremony, camera errors in the 2000 range, and errors about the user’s enrollment and biometric match in the 3000 range, such as 3003 when the user is not yet enrolled.
Liveness errors use the 4000 range, Recognize server errors the 5000 range, and device security errors the 6000 range.
The full set of codes is available as the RECOGNIZE_ERROR_CODE constant in the @forgerock/recognize package.