Enrollment
Enrollment is the process of registering a new user by connecting their facial biometrics to a PingOne Recognize account. During this process, a full and unobstructed view of the user’s face is required.
Enrollment with Web SDK can happen in two ways:
-
Live enrollment using the PingOne Recognize Web SDK JavaScript libraries.
-
IDV Bridge SaaS using the PingOne Recognize Authentication Service
This page explains how to perform interactive live enrollment on the front-end.
PingOne Recognize also supports component interoperability, so that users can be enrolled using IDV Bridge (on-premises) or the Mobile SDK. Authentication can be performed later using JavaScript and the Web SDK.
Before you begin
Review the prerequisites requirements before continuing.
Headless integration
The @keyless/sdk-web library lets you integrate the PingOne Recognize Web SDK without using a user interface. Here’s a simple enrollment:
Details
import {
addKeylessEventListeners,
createKeylessEnroll,
createKeylessMediaStream,
getKeylessCameraPermissionState,
getKeylessVideoMediaDevices,
getLastKeylessServerFrameTriggeredBiometricFilters,
getLastKeylessVideoFrameQuality,
importKeylessWebAssemblyModuleOrThrow,
isKeylessVideoMediaStreamAvailable,
KeylessError,
openKeylessWebSocketConnection,
reduceKeylessBiometricFiltersToTriggered,
removeKeylessEventListeners
} from '@keyless/sdk-web'
function requestTransactionJwtVerification(jwt) {}
function requestUserCameraPermission() {}
function handleCameraOperativityError(error) {}
function handleImportKeylessWebAssemblyModuleError(error) {}
function handleCreateKeylessMediaStreamError(error) {}
function handleOpenKeylessWebSocketConnectionError(error) {}
/**
* This event is fired when an error occurs during the enrollment process.
* The error object contains a `message` property that indicates the type of error.
*/
function onKeylessError(sym, error) {
/**
* Removing event listeners is advised on terminal events since
* no more than one attempt is allowed per enrollment symbol.
*/
removeKeylessEventListeners(sym)
// will log the error code
console.error(error.message)
}
/**
* This event is fired when the enrollment process is complete.
* It does not fire for failed attempts, only successful ones.
*/
function onKeylessFinished(sym, message) {
/**
* Removing event listeners is advised on terminal events since
* no more than one attempt is allowed per enrollment symbol.
*/
removeKeylessEventListeners(sym)
/**
* The `transactionJwt` is a JSON Web Token (JWT) that contains information
* about the enrollment transaction.
*
* This token is signed by the Keyless Authentication Service and can be used
* to verify the authenticity of the transaction.
*
* This operation is strictly backend-to-backend and should never be performed
* in client-side code.
*/
requestTransactionJwtVerification(message.transactionJwt)
}
/**
* This event is useful for providing real-time feedback to users during
* the enrollment process, such as prompting them to adjust their position
* or lighting conditions to improve biometric recognition.
*
* The difference with "onKeylessVideoFrameQuality" is that this is from
* filters running on the server.
*/
function onKeylessFrameResults(sym, message) {
let filters
/**
* Returns an array of biometric filters that were triggered in the last frame.
* If no biometric filters were triggered, an empty array is returned.
*/
filters = reduceKeylessBiometricFiltersToTriggered(message.filters)
/**
* Optionally, this function can be used to retrieve the filters that were triggered
* in the last frame.
*
* This can be useful if you need to access the last frame's triggered filters outside
* of the frame results event.
*
* If this function is used then this event is useful for requesting an update to the UI.
*/
filters = getLastKeylessServerFrameTriggeredBiometricFilters(sym)
}
/**
* This event is useful for providing real-time feedback to users during
* the enrollment process, such as prompting them to adjust their position
* or lighting conditions to improve biometric recognition.
*
* The difference with "onKeylessFrameResults" is that this is from filters
* running on the client.
*/
function onKeylessVideoFrameQuality(sym, event) {
/**
* Will log an array of filters that were triggered in this frame.
* If no biometric filters were triggered, an empty array is returned.
*/
console.log(event.filters)
/**
* Optionally, this function can be used to retrieve the quality of the last
* video frame.
*
* This can be useful if you need to access the last video frame quality outside
* of the video frame quality event.
*
* If this function is used then this event is useful for requesting an update to the UI.
*/
console.log(getLastKeylessVideoFrameQuality(sym))
}
async function ensureCameraOperativity() {
let devices, state
devices = await getKeylessVideoMediaDevices()
/**
* If the error is MEDIA_DEVICES_NO_VIDEO_INPUTS, it means that
* the user's device doesn't have a camera.
*/
if (devices instanceof Error && devices.message === KeylessError.MEDIA_DEVICES_NO_VIDEO_INPUTS) throw devices
state = await getKeylessCameraPermissionState()
/**
* If the camera permission state is not 'granted', request
* the user to grant camera access.
*/
if (state !== 'granted') {
/**
* Ideally this function should take the user to a UI prompt
* where they can grant camera access to the website.
*
* The easiest way to trigger the browser's camera permission prompt
* is to call isKeylessVideoMediaStreamAvailable(), which will return
* a boolean indicating whether the user granted camera access or not.
*/
requestUserCameraPermission()
throw new Error('camera permission state is not granted')
}
devices = await getKeylessVideoMediaDevices()
/**
* If the error is MEDIA_DEVICES_EMPTY_VIDEO_INPUT_LABEL, it means that
* even though the user has granted camera access, the browser requires
* the user to start a video stream to be able to read the camera labels.
*
* In this case, we perform a throwaway getUserMedia() request with
* isKeylessVideoMediaStreamAvailable() to start a video stream
* to be able to read the camera labels.
*/
if (devices instanceof Error && devices.message === KeylessError.MEDIA_DEVICES_EMPTY_VIDEO_INPUT_LABEL) {
let available
available = await isKeylessVideoMediaStreamAvailable()
if (!available) throw new Error('video media stream is not available')
devices = await getKeylessVideoMediaDevices()
}
/**
* If we still have an error, throw an error to indicate that
* the media devices still could not be read correctly.
*/
if (devices instanceof Error) throw devices
}
async function enrollWithKeyless() {
let enroll, options, stream, open
/**
* Create a Keyless enrollment symbol.
*
* This symbol must be kept in memory for the duration of the enrollment process.
* To perform multiple enrollments, a new symbol must be created for each enrollment.
*/
enroll = createKeylessEnroll()
/**
* Add event listeners through the Keyless enrollment symbol.
* These listeners will handle events during the enrollment process.
*/
addKeylessEventListeners(enroll, [
{ name: 'error', callback: (error) => onKeylessError(enroll, error) },
{ name: 'finished', callback: (message) => onKeylessFinished(enroll, message) },
{ name: 'frame-results', callback: (message) => onKeylessFrameResults(enroll, message) },
{ name: 'video-frame-quality', callack: (event) => onKeylessVideoFrameQuality(enroll, event) }
])
options = {
authorization: {
token: 'USER_AUTHORIZATION_FROM_CUSTOMER'
},
customer: { name: 'CUSTOMER_NAME' },
key: { id: 'IMAGE_ENCRYPTION_KEY_ID', value: 'IMAGE_ENCRYPTION_PUBLIC_KEY' },
transaction: {
data: 'DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
},
username: 'USERNAME',
ws: { url: 'KEYLESS_AUTHENTICATION_SERVICE_URL' }
}
/**
* Create a media stream from the user's video input media device.
* This stream will be used to capture video frames for biometric analysis.
*
* Note: The user must grant permission to access the media device.
*/
stream = await createKeylessMediaStream()
if (stream instanceof Error) return handleCreateKeylessMediaStreamError(stream)
/**
* Open a WebSocket connection to the Keyless Authentication Service.
* This connection will be used to process video frames and receive enrollment results.
*/
open = await openKeylessWebSocketConnection(enroll, options)
if (open instanceof Error) return handleOpenKeylessWebSocketConnectionError(open)
}
importKeylessWebAssemblyModuleOrThrow()
.then(() =>
ensureCameraOperativity()
.then(() => enrollWithKeyless())
.catch(handleCameraOperativityError)
)
.catch(handleImportKeylessWebAssemblyModuleError)
Web component integration
This section shows how to integrate the PingOne Recognize Web SDK directly into a web application.
-
React
-
Vue
-
Embedded
import '@keyless/sdk-web-components'
export function KeylessEnroll() {
function requestTransactionJwtVerification(jwt) {}
onError = (event) => {
// will log the error code
console.log(event.message)
}
onFinished = (event) => {
/**
* The `transactionJwt` is a JSON Web Token (JWT) that contains information
* about the enrollment transaction.
*
* This token is signed by the Keyless Authentication Service and can be used
* to verify the authenticity of the transaction.
*
* This operation is strictly backend-to-backend and should never be performed
* in client-side code.
*/
requestTransactionJwtVerification(message.transactionJwt)
}
return (
<kl-enroll
authorization-token='USER_AUTHORIZATION_FROM_CUSTOMER'
customer='CUSTOMER_NAME'
enable-camera-instructions
key-id='IMAGE_ENCRYPTION_KEY_ID'
lang='en'
onerror={onError}
onfinished={onfinished}
public-key='IMAGE_ENCRYPTION_PUBLIC_KEY'
size='375'
theme='light'
transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
username='USERNAME'
ws-url='KEYLESS_AUTHENTICATION_SERVICE_URL'
/>
)
}
<script setup>
import '@keyless/sdk-web-components'
function requestTransactionJwtVerification(jwt) {}
function onError(event) {
// will log the error code
console.log(event.message)
}
function onFinished(event) {
/**
* The `transactionJwt` is a JSON Web Token (JWT) that contains information
* about the enrollment transaction.
*
* This token is signed by the Keyless Authentication Service and can be used
* to verify the authenticity of the transaction.
*
* This operation is strictly backend-to-backend and should never be performed
* in client-side code.
*/
requestTransactionJwtVerification(message.transactionJwt)
}
</script>
<template>
<kl-enroll
customer="CUSTOMER_NAME"
enable-camera-instructions
@error="onError"
@finished="onFinished"
key="IMAGE_ENCRYPTION_PUBLIC_KEY"
key-id="IMAGE_ENCRYPTION_KEY_ID"
lang="en"
size="375"
theme="light"
transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
username="USERNAME"
ws-url="KEYLESS_AUTHENTICATION_SERVICE_URL"
/>
</template>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Enroll</title>
<style>
* {
box-sizing: border-box;
}
body {
align-items: center;
display: flex;
justify-content: center;
margin: 0;
min-height: 100vh;
padding: 8px;
}
kl-enroll {
border: 1px solid lightgray;
}
</style>
</head>
<body>
<kl-enroll
customer="CUSTOMER_NAME"
enable-camera-instructions
key="IMAGE_ENCRYPTION_PUBLIC_KEY"
key-id="IMAGE_ENCRYPTION_KEY_ID"
lang="en"
size="375"
theme="light"
transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
username="USERNAME"
ws-url="KEYLESS_AUTHENTICATION_SERVICE_URL"
></kl-enroll>
<script src="./node_modules/@keyless/sdk-web-components/index.js" type="module"></script>
<script>
const enroll = document.querySelector('kl-enroll')
function requestTransactionJwtVerification(jwt)
enroll.addEventListener('error', (event) => {
// will log the error code
console.log(event.message)
})
enroll.addEventListener('finished', (event) => {
/**
* The `transactionJwt` is a JSON Web Token (JWT) that contains information
* about the enrollment transaction.
*
* This token is signed by the Keyless Authentication Service and can be used
* to verify the authenticity of the transaction.
*
* This operation is strictly backend-to-backend and should never be performed
* in client-side code.
*/
requestTransactionJwtVerification(message.transactionJwt)
})
</script>
</body>
</html>