# Authentication

Authentication is the biometric equivalent of "signing-in". During authentication Keyless compares the user's facial biometrics with the ones computed during [enrollment](/web-sdk/web-sdk-guide/enrollment.md).

If the biometrics match, Keyless authenticates the user.

### Prerequisites

Make sure you have fulfilled the prerequisites listed in the [Prerequisites](/web-sdk/web-sdk-guide/prerequisites.md) 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:

```javascript
import {
  addKeylessEventListeners,
  createKeylessAuth,
  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 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.
 *
 * 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 = getLastKeylessFrameTriggeredBiometricFilters(sym)
}

/**
 * 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.
 *
 * 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 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.

{% tabs %}
{% tab title="React" %}

```jsx
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'
    />
  )
}
```

{% endtab %}

{% tab title="Vue" %}

```html
<script setup>
import '@keyless/sdk-web-components'

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 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)
}
</script>

<template>
  <kl-auth
    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>
```

{% endtab %}

{% tab title="Embedded" %}

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Auth</title>
    <style>
      * {
        box-sizing: border-box;
      }

      body {
        align-items: center;
        display: flex;
        justify-content: center;
        margin: 0;
        min-height: 100vh;
        padding: 8px;
      }

      kl-auth {
        border: 1px solid lightgray;
      }
    </style>
  </head>
  <body>
    <kl-auth
      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-auth>
    <script src="./node_modules/@keyless/sdk-web-components/index.js" type="module"></script>
    <script>
      const auth = document.querySelector('kl-auth')
      
      auth.addEventListener('error', (event) => {
        // will log the error code
        console.log(event.message)
      })

      auth.addEventListener('finished', (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)
      })
    </script>
  </body>
</html>
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.keyless.io/web-sdk/web-sdk-guide/authentication.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
