Authentication

Authentication is the biometric equivalent of "signing-in". During authentication Keyless compares the user's facial biometrics with the ones computed during enrollment.

If the biometrics match, Keyless authenticates the user.

Prerequisites

Make sure you have fulfilled the prerequisites listed in the Prerequisites page before going further.

Headless Integration

The @keyless/sdk-web library lets you integrate the Keyless Web SDK however you want in terms of UI/UX, let's see a basic example of authentication:

import {
  addKeylessEventListeners,
  createKeylessAuth,
  createKeylessMediaStream,
  getKeylessCameraPermissionState,
  getKeylessVideoMediaDevices,
  getLastKeylessFrameTriggeredBiometricFilters,
  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 authentication process.
 * The error object contains a `code` 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 authentication symbol.
   */
  removeKeylessEventListeners(sym)

  // will log the error code
  console.error(error.message)
}

/**
 * This event is fired when the authentication 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 authentication symbol.
   */
  removeKeylessEventListeners(sym)

  /**
   * The `transactionJwt` is a JSON Web Token (JWT) that contains information
   * about the authentication 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 authentication process, such as prompting them to adjust their position
 * or lighting conditions to improve biometric recognition.
 */
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 = getLastKeylessFrameTriggeredBiometricFilters(sym)
}

async function ensureCameraOperativity() {
  let devices, state

  devices = await getKeylessVideoMediaDevices()

  /**
   * If the error is MEDIA_DEVICES_NO_VIDEO_INPUTS, it means that
   * the user does not have any camera available.
   */
  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 authenticateWithKeyless() {
  let imprt, auth, options, stream, open

  /**
   * Create a Keyless authentication symbol.
   *
   * This symbol must be kept in memory for the duration of the authentication process.
   * To perform multiple authentications, a new symbol must be created for each authentication.
   */
  auth = createKeylessAuth()

  /**
   * Add event listeners through the Keyless authentication symbol.
   * These listeners will handle events during the authentication process.
   */
  addKeylessEventListeners(auth, [
    { name: 'error', callback: (error) => onKeylessError(auth, error) },
    { name: 'finished', callback: (message) => onKeylessFinished(auth, message) },
    { name: 'frame-results', callback: (message) => onKeylessFrameResults(auth, message) }
  ])

  options = {
    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 authentication results.
   */
  open = await openKeylessWebSocketConnection(auth, options)
  if (open instanceof Error) return handleOpenKeylessWebSocketConnectionError(open)
}

importKeylessWebAssemblyModuleOrThrow()
  .then(() =>
    ensureCameraOperativity()
      .then(() => authenticateWithKeyless())
      .catch(handleCameraOperativityError)
  )
  .catch(handleImportKeylessWebAssemblyModuleError)

Web Component Integration

Previously we explained how to perform authentication in a headless way with the @keyless/sdk-web package, now we'll provide an example of doing so with raw HTML syntax.

import '@keyless/sdk-web-components'

export function KeylessAuth() {
  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 authentication 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-auth
      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'
    />
  )
}

Last updated

Was this helpful?