Only this pageAll pages
Powered by GitBook
1 of 32

Mobile SDK

Loading...

Introduction

Loading...

Loading...

Loading...

Mobile SDK Guide

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Mobile SDK Reference

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Mobile SDK Use Cases

Loading...

Loading...

Loading...

Mobile SDK Changelog

Loading...

Server API

Loading...

Loading...

Loading...

Loading...

Components

Learn about the interaction between Keyless components

The diagrams below show how the Keyless SDK, which runs within your mobile app on the user’s device, interacts with your application server and with the Keyless network.

Enrollment

During enrollment, your mobile app invokes the enroll method from the Keyless SDK, and then:

  1. Guides the user through capturing a biometric signal with the device camera.

  2. Interacts with Keyless to generate a new user identifier (Keyless ID), which is then returned to your mobile app.

Figure 1: Keyless enrollment diagram

Authentication

Authentication involves your application server, your mobile app, and the Keyless network, as depicted in Figure 2:

  1. This process starts when the user performs an action that requires authentication using your mobile app.

  2. The app provides the details of this action to your application server, which generates a challenge. The challenge is sent to the mobile app, which uses the Keyless SDK to compute the corresponding authentication token using the authenticate method.

  3. The Keyless SDK authenticates the user by capturing the user’s biometrics using the mobile device’s camera.

  4. The Keyless SDK connects to the Keyless network, and runs a secure multi-party computation protocol that authenticates the user and generates the authentication token in response to the challenge provided in Step 2. The Keyless SDK returns the authentication token to the mobile app.

  5. The app sends the token to your application server, which verifies it.

  6. If the authentication token is valid, the application server completes the transaction and notifies your mobile app.

Figure 2: Keyless authentication diagram

Account Deletion

Account deletion is similar to authentication. First, your mobile app performs authentication steps 1-6, then it notifies your application server that the user wants to delete the account. Next, your mobile app invokes the deEnroll method from the Keyless SDK. This method issues a deletion request to the Keyless network (Step 7). The request removes all data associated with the user from the Keyless network.

Figure 3: Keyless de-enrollment diagram

Introduce Keyless to Users

In order to introduce Keyless to users before the very first enrollment, it may be helpful to show an explanatory screen on what is the flow about to start.

This screen is showable via the public API Keyless.showIntroductionScreen. This function can be called at any moment (even before setup). It expects a completion for when the user taps on the main CTA. The message can be customized as explained in UI-Customization

Examples

Keyless.showIntroductionScreen { 
    // Perform actions after user taps CTA
}
Keyless.showIntroductionScreen { 
    // Perform actions after user taps CTA
}

Devices

This set of API calls allows you to fetch and manipulate Keyless enabled devices.

4️⃣ De-Enrollment

De-enrollment is the biometric equivalent of an account deletion. Keyless performs an to compare the user's facial biometrics with the ones computed during . If the biometrics match, the user is authenticated and their account will be removed from Keyless. This operation is irreversible.

DeEnrollment configuration

Camera Delay

Use cameraDelaySeconds to specify the delay (in seconds) between when the camera preview appears, and when the liveness processing starts.

Success Feedback

Use showSuccessFeedback to show a Success text on top of the screen when the DeEnroll is successful.

Liveness Settings

Using livenessConfiguration you can configure the liveness security level during enrollment. The possible liveness configuration are under LivenessSettings.LivenessConfiguration :

You can also specify a livenessEnvironmentAware that is by default se to true to enhance liveness detection. This parameters helps to ensure the user is in a suitable setting for verification.

More details on liveness in the dedicated section.

6️⃣ User and device management

The Keyless SDK "caches" the enrolled user locally on the device.

There are some use cases where it is possible to and . The Keyless SDK will not be notified about such deletions. For this reason if you try to authenticate a user or a device that have been deleted from server API you will get an error.

Call validateUserAndDeviceActive before authenticating, to validate that both the user and the device are still active in the Keyless backend, to avoid asking the user for biometric data which will still not let them authenticate.

User identifier

Retrieve the user identifier with Keyless.getUserId():

Device identifier

The device is identified by its public signing key. To retrieve the public signing key use Keyless.getDevicePublicSigningKey():

Keyless SDK reset

Resetting the Keyless SDK to a clean state deletes local data from the device, but does not de-enoll the user from the Keyless backend or deactivate the device from the Keyless backend:

Lockout management

The getRateLimitInfo API checks whether the user is currently rate-limited and, if so, for how many seconds. This API is typically used to provide feedback to users after multiple failed authentication attempts.

Integration Flows

Learn how the Keyless components interact with your app.

Introduction

Learn how the Keyless SDK components can be integrated into a mobile application and backend server, to enable biometric authentication.

Integration Overview

Enrollment Flow

To authenticate with Keyless, a user must first enroll their biometric template. Enrollment with Keyless consists of registering the user’s biometric features in a privacy-preserving manner using the various from the Keyless SDK.

Authentication Flow

The most common authentications scenarios for the Keyless SDK are:

  • access to a web application

  • access on a mobile application

Authentication in a web application

In this scenario the user is trying access to a resource in a web application for which strong authentication is required. The web application backend sends a push notification to the customer app to request that the user identify themselves with Keyless. After biometric authentication is successful, the flow returns to the web application backend, which leverages the APIs exposed by the Keyless backend to perform additional security checks.

Once the Keyless backend confirms that the authentication was successful, the user is allowed access to the resource.

Authentication on a mobile app

In this scenario the user is trying access to a resource directly in the mobile application for which strong authentication is required. The mobile application sends a push notification to the customer app to request that the user identify themselves with Keyless. After biometric authentication is successful, the flow returns to the mobile application backend, which leverages the APIs exposed by the Keyless backend to perform additional security checks.

Once the Keyless backend confirms that the authentication was successful, the user is allowed access to the resource.

Keyless Components

As mentioned, Keyless is composed of two main blocks:

  • Keyless SDK

  • Keyless backend / Confirmation API Service

Keyless SDK

The Keyless SDK supports both Android and iOS, and exposes API methods to interact with the Keyless Privacy-Preserving Network to perform the following actions:

  • Enroll a user

  • Authenticate

  • De-Enroll

  • Restore backup

Keyless backend

The Keyless SaaS backend offers APIs which can be used to perform security checks through Backend-to-Backend calls. Specifically, it is possible to interrogate the Keyless Backend after the SDK returns an OK response for an Authentication attempt.

JWT signing

Keyelss mobile SDK can generate a signed a JWT containing a custom payload. You can use the signed JWT to implement .

Generate signed JWT

Pass JwtSigningInfo to the authentication to generate a signed JWT:

User signing public key

The AuthenticationSuccess contains the following fields:

  • signedJwt: the signed JWT.

Account recovery

This page explains what we mean by account recovery and the context you may need to integrate it.

What is Account Recovery?

Account Recovery is the term we use to describe the use case where a user is known to our customer (i.e. is registered in Keyless), but needs to be authenticated on a new device. Typically this is either where:

  • They had previously been enrolled on a device but the user no longer has access to it.

  • They are adding a back-up device.

  • They are known to our customer, perhaps having submitted a selfie during onboarding, but have yet to authenticate on a device via our Mobile SDK within our customer's app (which would have to leverage ).

Client or Temporary State

Keyless is able to recover an account from what we refer to as client or temporary state.

The client or temporary state is obtained:

  1. From your backend through Keyless IDVBridge.

    • To understand how to generate this state see our or

    • Then head to to understand how to leverage this state to bind a userID to a new device.

  2. From your client app using the Keyless Mobile SDK:

    • during live enrollment or authentication.

    • Then head to to understand how to leverage this state to bind a userID to a new device.

enrollment methods
Keyless IDV Bridge
IDV Bridge SaaS
IDV On-Premise
New Device Activation
Generate the temporary state
New Device Activation
val configuration = DeEnrollmentConfiguration.builder.build()

Keyless.deEnroll(
    deEnrollmentConfiguration = configuration,
    onCompletion = { result ->
        when (result) {
            is Keyless.KeylessResult.Success -> Log.d("KeylessSDK ", "De-enroll success")
            is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "De-enroll failure - error code ${result.error.code}")
        }
    }
)
let configuration = Keyless.DeEnrollmentConfiguration.builder
    .build()

Keyless.deEnroll(
    deEnrollmentConfiguration: configuration,
    onCompletion: { error in
        if let error = error {
              print("De-Enrollment finished with error: \(error.message)")
        } else {
              print("De-Enrollment finished with success")
        }
    }
}
import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/deenrollment_configuration.dart';

// Biometric de-enrollment
final configuration = BiomDeEnrollConfig(
    livenessConfiguration: LivenessConfiguration.passiveStandaloneHigh,
    livenessTimeout: 60,
    cameraDelaySeconds: 0
);

// Or PIN-based de-enrollment
final configuration = PinDeEnrollConfig(
    pin: "1234"
);

try {
    await Keyless.instance.deEnroll(configuration);
    print("De-enrollment successful");
} catch (error) {
    print("De-enrollment failed: $error");
}
PASSIVE_STANDALONE_MEDIUM
PASSIVE_STANDALONE_HIGH        //recommended configuration
PASSIVE_STANDALONE_HIGHEST
authentication
enrollment
liveness settings
try {
  await Keyless.validateUserAndDeviceActive();
  print("user and device active");
} catch (error) {
  print("user or device deactivated - error code: ${error.code}");
  // error code 1131 = user is not enrolled on the device (not even locally so did not check on backend)
  // error code 534 = user not found or deactivated on backend
  // error code 535 = device not found or deactivated on backend
}
fun getDevicePublicSigningKey(): KeylessResult<ByteArray, KeylessSdkError>
func getDevicePublicSigningKey() -> Result<String, KeylessSDKError>
Future<String> getDevicePublicSigningKey() async
fun reset(
  onCompletion: (KeylessResult<Unit, KeylessSdkError>) -> Unit
)
func reset() -> KeylessSDKError?
Future<void> reset() async
Keyless.getRateLimitInfo { result ->
    when (result) {
        is Keyless.KeylessResult.Success -> {
            val rateLimitInfo = result.value
            println("User is rate limited: ${rateLimitInfo.isRateLimited} with remaining seconds: ${rateLimitInfo.remainingSeconds}")
        }
        is Keyless.KeylessResult.Failure -> {
            println("Error: ${result.error.message}")
        }
    }
}
Keyless.getRateLimitInfo(completion: { result in
        switch result {
        case .success(let success):
            print("User is rate limited: \(success.isRateLimited) with remaining seconds: \(success.remainingSeconds)")
        case .failure(let error):
            print("Error: \(error.message)")
        }
})
try {
    final rateLimitInfo = await Keyless.instance.getRateLimitInfo();
    print("User is rate limited: ${rateLimitInfo.isRateLimited} with remaining seconds: ${rateLimitInfo.remainingSeconds}");
} catch (error) {
    print("Error: $error");
}
delete the user from server API
delete the device from server API
import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/jwt_signing_info.dart';
import 'package:keyless_flutter_sdk/models/configurations/authentication_configuration.dart';

// Keyless adds a td claim to the JWTs containing the data you specify
final jwtSigningInfo = JwtSigningInfo("<your custom data>");

// if you want to authenticate with biometric
final biomAuthConfig = BiomAuthConfig(
    jwtSigningInfo: jwtSigningInfo
);

// if you want to authenticate with pin
final pinAuthConfig = PinAuthConfig(
    pin: "1234",
    jwtSigningInfo: jwtSigningInfo
);

try {
    // perform the authentication with either biometric or pin config
    final result = await Keyless.instance.authenticate(
        biomAuthConfig // or pinAuthConfig if you use pin
    );
    print("JWT signed successfully: ${result.signedJwt}");
} catch (error) {
    print("Authentication failed: $error");
}
Dynamic Linking

Liveness Settings

Liveness Configuration

Keyless SDK provides three officially-supported configurations for the liveness detection (antispoofing component), listed below from the lowest to the highest level of security:

  • PASSIVE_STANDALONE_MEDIUM - for testing purposes only

  • PASSIVE_STANDALONE_HIGH - for most production use (SDK default)

  • PASSIVE_STANDALONE_HIGHEST - for higher security production use

Increasing the security level increases the ability of the system to reject spoof attempts (true positive rate, or TPR). A higher security level also increases the genuine reject rate (false positive rate, or FPR) and the time required by the anti-spoofing module to make a decision.

For most production scenarios, Keyless recommends the use of PASSIVE_STANDALONE_HIGH. This setting offers a good tradeoff between TPR, FPR, and time-to-decision. For scenarios that require a higher security level, we recommend increasing this setting to PASSIVE_STANDALONE_HIGHEST.

Liveness Awareness

You can also specify a livenessEnvironmentAware that is by default se to true to enhance liveness detection. This parameters helps to ensure the user is in a suitable setting for verification.

Note that we have observed that this may prevent some users on a very limited set of devices from authenticating with Keyless. In this case the SDK will return an 20021 error.

The livenessEnvironmentAware feature enforces stricter environmental checks during the liveness verification process. We recommend keeping this setting enabled, and it defaults to true for enhanced security. Users migrating from SDK versions earlier than v5.0.5 might experience a higher rate of liveness rejections, as this stricter validation is now automatic.

To provide flexibility, we have also included the option to disable this check but please contact us if you want to discuss the potential impact of doing so.

Relax liveness checks for testing purposes

Below follows a liveness configuration example for testing pruposes only should facilitate testing the happy path of "passing the liveness checks".

// ONLY FOR TEST

// Authentication Configuration
val authConfig = BiomAuthConfig(
        livenessConfiguration = LivenessSettings.LivenessConfiguration.PASSIVE_STANDALONE_MEDIUM,
        livenessEnvironmentAware = false
)


// Enrollment Configuration
val enrollConfig = BiomEnrollConfig(
        livenessConfiguration = LivenessSettings.LivenessConfiguration.PASSIVE_STANDALONE_MEDIUM,
        livenessEnvironmentAware = false
)


// De-Enrollment Configuration
val deEnrollConfig = BiomDeEnrollConfig(
        livenessConfiguration = LivenessSettings.LivenessConfiguration.PASSIVE_STANDALONE_MEDIUM,
        livenessEnvironmentAware = false
)
// ONLY FOR TEST

// Authentication Configuration
let authConfig = BiomAuthConfig(
        livenessConfiguration: Keyless.LivenessConfiguration.passiveStandaloneMedium
        livenessEnvironmentAware: false
)

// Enrollment Configuration
let enrollConfig = BiomEnrollConfig(
        livenessConfiguration: Keyless.LivenessConfiguration.passiveStandaloneMedium
        livenessEnvironmentAware: false
)

// De-Enrollment Configuration
let deEnrollConfig = BiomDeEnrollConfig(
        livenessConfiguration: Keyless.LivenessConfiguration.passiveStandaloneMedium
        livenessEnvironmentAware: false
)
// ONLY FOR TEST


// Authentication Configuration
final authConfig = BiomAuthConfig(
    livenessConfiguration: LivenessConfiguration.PASSIVE_STANDALONE_MEDIUM
    // livenessEnvironmentAware: false - not available yet
);

// Enrollment Configuration
final enrollConfig = BiomEnrollConfig(
    livenessConfiguration: LivenessConfiguration.PASSIVE_STANDALONE_MEDIUM
    // livenessEnvironmentAware: false - not available yet

);

// De-Enrollment Configuration
final deEnrollConfig = BiomDeEnrollConfig(
    livenessConfiguration: LivenessConfiguration.PASSIVE_STANDALONE_MEDIUM
    // livenessEnvironmentAware: false - not available yet
);

Generating temporary state via the Mobile SDK

This page explains how to create a temporary state during a Keyless Enrollment or Authentication, which can then be leveraged for binding a user on a new device.

What is the temporary state?

The Keyless temporary state (referred to as the client state in other contexts) contains all the necessary information to restore an account. It can be created during enrollment and authentication

To create and use the temporary state Keyless requires the user's biometric to be authenticated.

The temporay state internals are not important but you can expect a string similar to the following that you should pass as-is to recover the account:

"{\"artifact\":{\"family\":\"davideface_lite\",\"version\":\"1.2.0\",\"target\":\"mobile_sdk\",\"liveness\":\"liveness\"},\"core-client-state\":\"BASE_64_STATE\"}"

Note that when generating the temporary state within your client app using the Keyless Mobile SDK a maximum of 50 temporary states are allowed to be generated. This ensures that the experience remains performant.

Obtain the temporary state

Use the shouldRetrieveTemporaryState parameter of the BiomEnrollConfig or BiomAuthConfig depending if you want to retrieve the temporary state during enrollment or authencation flows.

Dunring the enrollment flow:

During the authentication flow:

During the enrollment flow:

let enrollConfig = BiomEnrollConfig(shouldRetrieveTemporaryState: true)

Keyless.enroll(
  configuration: enrollConfig,
  onCompletion: { result in
    switch result {
    case .success(let enrollSuccess):
    	
    	let temporaryState = enrollSuccess.temporaryState
    	// store the temporary state on your backend to recover the account in the future

    case .failure(let error):
        print("error code: \(error.code)
    }
  })

During the authentication flow:

let authConfig = BiomAuthConfig(shouldRetrieveTemporaryState: true)

Keyless.authenticate(
  configuration: authConfig,
  onCompletion: { result in
    switch result {
    case .success(let authSuccess):
    	
    	let temporaryState = authSuccess.temporaryState
    	// store the temporary state on your backend to recover the account in the future

    case .failure(let error):
        print("error code: \(error.code)
    }
  })

During the enrollment flow:

import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/enrollment_configuration.dart';

final enrollConfig = BiomEnrollConfig(shouldRetrieveTemporaryState: true);

try {
    final result = await Keyless.instance.enroll(enrollConfig);
    if (result.temporaryState != null) {
        // store the temporary state on your backend to recover the account in the future
        print("Temporary state retrieved: ${result.temporaryState}");
    }
} catch (error) {
    print("Enrollment failed: $error");
}

During the authentication flow:

import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/authentication_configuration.dart';

final authConfig = BiomAuthConfig(shouldRetrieveTemporaryState: true);

try {
    final result = await Keyless.instance.authenticate(authConfig);
    if (result.temporaryState != null) {
        // store the temporary state on your backend to recover the account in the future
        print("Temporary state retrieved: ${result.temporaryState}");
    }
} catch (error) {
    print("Authentication failed: $error");
}

Operations

This set of API calls allows you to fetch operations on various Keyless entities.

Operations

JWT

Users

This set of API calls allows you to make operations on various Keyless users.

val enrollConfig = BiomEnrollConfig(shouldRetrieveTemporaryState = true)

Keyless.enroll(
  configuration = enrollConfig,
  onCompletion = { result ->
    when (result) {
      is Keyless.KeylessResult.Success -> {

      	val temporaryState = result.value.temporaryState
      	// store the temporary state on your backend to recover the account in the future

      }
      is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "error code ${result.error.code}")
    }
  }
)
val authConfig = BiomAuthConfig(shouldRetrieveTemporaryState = true)

Keyless.authenticate(
  configuration = authConfig,
  onCompletion = { result ->
    when (result) {
      is Keyless.KeylessResult.Success -> {

      	val temporaryState = result.value.temporaryState
      	// store the temporary state on your backend to recover the account in the future

      }
      is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "error code ${result.error.code}")
    }
  }
)
val configuration = BiomDeEnrollConfig()

Keyless.deEnroll(
    deEnrollmentConfiguration = configuration,
    onCompletion = { result ->
        when (result) {
            is Keyless.KeylessResult.Success -> Log.d("KeylessSDK ", "De-enroll success")
            is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "De-enroll failure - error code ${result.error.code}")
        }
    }
)
let configuration = BiomDeEnrollConfig()
    .build()

Keyless.deEnroll(
    deEnrollmentConfiguration: configuration,
    onCompletion: { error in
        if let error = error {
              print("De-Enrollment finished with error: \(error.message)")
        } else {
              print("De-Enrollment finished with success")
        }
    }
}
Keyless.validateUserAndDeviceActive(
    onCompletion = { result ->
        when (result) {
            is Keyless.KeylessResult.Success -> Log.d("KeylessSDK ", "user and device active")
            is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "user or device not ofund - error code ${result.error.code}")
            // error code 1131 = user is not enrolled on the device (not even locally so did not check on backend)
            // error code 534 = user not found or deactivated on backend
            // error code 535 = device not found or deactivated on backend
        }
    }
)
fun getUserId(): KeylessResult<String, KeylessSdkError>
Future<String> getUserId() async
Keyless.validateUserDeviceActive(
            completionHandler: { error in
                if let error = error {
                    print("user or device deactivated")
                       // error code 1131 = user is not enrolled on the device (not even locally so did not check on backend)
                       // error code 534 = user not found or deactivated on backend
                       // error code 535 = device not found or deactivated on backend
                } else {
                    print("user and device active")
                }
            }
        )
func getUserId() -> Result<String, KeylessSDKError>
//Keyless adds a td claim to the JWTs containing the data you specify
val jwtSigningInfo = JwtSigningInfo(claimTransactionData = "<your custom data")

// if you want to authenticate with biometric
val biomAuthConfig = BiomAuthConfig(jwtSigningInfo = jwtSigningInfo)
// if you want to authenticate with pin
val pinAuthConfig = PinAuthConfig(pin = "1234", jwtSigningInfo = jwtSigningInfo)

// perform the authentication
Keyless.authenticate(
    configuration = biomAuthConfig, // pinAuthConfig if you use pin
    onCompletion = { /*TODO: process result*/ }
)
//Keyless adds a td claim to the JWTs containing the data you specify
let jwtSigningInfo = JwtSigningInfo(claimTransactionData: "<your custom data>")

// if you want to authenticate with biometric
let biomAuthConfig = BiomAuthConfig(jwtSigningInfo: jwtSigningInfo)
// if you want to authenticate with pin
let pinAuthConfig = PinAuthConfig(pin: "1234", jwtSigningInfo: jwtSigningInfo)

// perform the authentication
Keyless.authenticate(
    configuration: biomAuthConfig, // pinAuthConfig if you use pin
    onCompletion : { /*TODO: process result*/ }
)

Error handling

The Keyless SDK uses three classes of errors, each error has an error code and an error message. Errors follow 3 main categories:

  • Internal errors: triggered by Keyless internals.

  • Integration errors: triggered by a KeylessSDK integration misconfiguration.

  • User errors: triggered by unintended user behavior.

If you're implementing the Keyless SDK, you should handle errors coming from the SDK since the error message is not intended for end users.

Internal errors

Internal errors have code that are lower than 20_000. Internal errors require investigation from Keyless. If errors persist, please keep the error code, error message and stacktrace and contact us.

Integration errors

Integration errors have codes that span from 20_000 to 30_000. Integration errors can be solved by making sure you are not misusing the API surface of the SDK. You can solve it by reading the error message and addressing the issue. If errors persist, please keep the error code, error message and stacktrace and contact us.

User errors

Note that many of these errors are predictions only, when writing messages for your users it's often best to assume positive intent. For example, suggest that they make sure that nothing is obstructing the camera instead of suggesting that they are attempting to spoof.

User errors have code that are higher than 30_000. Intergration errors have code between 20_000 and 30_000.

Error
Code
Description

Spoofing

30000

The user might be placing a picture or a video in front of the camera.

Timeout

30001

The face could not be recognized before the specified timed out.

Mask detected

30002

The user might be wearing a mask, or there might be something hiding their face. Note: mask detected will be part of live feedback and no longer returned as an error from SDK version 4.8.0 and above.

User cancelled

30003

The user manually cancelled the face recognition and processing.

Face not matching

30004

The face of user in front of the camera does not match the face currently enrolled with Keyless.

No network connection

30005

The device appears to be offline.

Device tampered

30006

The device seems tampered and could have been rooted or jailbroken.

User lockout

30007

The user is temporarily locked out of Keyless after too many failed authentication attempts. In case of an enroll using the [temporary state](https://docs.keyless.io/consumer/mobile-sdk-guide/enrollment#temporary-state) the server could apply the lockout and in that case you would see an internal error with code `523`.

Rejected

30008

Keyless did not manage to recognize the user but does not suspect any spoofing attempt.

Camera denied

30009

The user denied camera permission.

Liveness Environment Aware

20021

The device does not meet the requirements for environment-aware liveness detection.

Examples

val configuration = Keyless.AuthenticationConfiguration()

Keyless.authenticate(
    authenticationConfiguration = configuration,
    onCompletion = { result ->
        when (result) {
            is Keyless.KeylessResult.Success -> {
                Log.d("IntegratorActivity ", "Authenticate success")
            }
            is Keyless.KeylessResult.Failure -> {
                when (result.error) {
                    is KeylessUserError.FaceNotMatching -> Log.d("IntegratorActivity ", "Face not matching")
                    is KeylessUserError.MaskDetected -> Log.d("IntegratorActivity ", "Mask detected")
                    is KeylessUserError.Spoofing -> Log.d("IntegratorActivity ", "Spoofing detected")
                    is KeylessUserError.Timeout -> Log.d("IntegratorActivity ", "The operation timed out")
                    is KeylessUserError.UserCancelled -> Log.d("IntegratorActivity ", "The user cancelled the operation")
                    is KeylessUserError.NoNetworkConnection -> Log.d("IntegratorActivity ", "No network connection available")
                    is KeylessUserError.Lockout -> Log.d("IntegratorActivity ", "Your account is temporarily locked")
                    else -> {
                        Log.d("IntegratorActivity ", "Authenticate failure")

                        val errorCode = result.error.code
                        val errorMessage = result.error.message
                        val errorCause = result.error.cause?.printStackTrace()
                        // here you could display a generic error popup with the error code
                    }
                }
            }
        }
    }
)
let configuration = Keyless.AuthenticationConfiguration.builder.build()

Keyless.authenticate(authenticationConfiguration: configuration) { result in
    switch result {
    case .success(let authenticationSuccess):
        print("authenticationDidFinish:  \(authenticationSuccess.token)")
    case .failure(let error):
        switch error.kind {
        case .userError(let userError):
            switch userError {
            case .faceNotMatching:
                print("Face not matching")
            case .maskDetected:
                print("Mask detected")
            case .spoofing:
                print("Spoofing detected")
            case .timeout:
                print("The operation timed out")
            case .userCancelled:
                print("The user cancelled the operation")
            case .noNetworkConnection:
                print("No network connection available")
            case .lockout:
                print("Your account is temporarily locked")
            }
        default:
            let code = error.code
            let message = error.message
            // here you could display a generic error popup with the error code
        }
    }
}
import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/authentication_configuration.dart';

final configuration = BiomAuthConfig();

try {
  final result = await Keyless.instance.authenticate(configuration);
  print("Authentication successful");
} catch (error) {
  if (error is KeylessError) {
    switch (error.errorType) {
      case KeylessErrorType.user:
        if (error.code == KeylessErrorCase.faceNotMatching.code) {
          print("Face not matching");
        } else if (error.code == KeylessErrorCase.maskDetected.code) {
          print("Mask detected");
        } else if (error.code == KeylessErrorCase.spoofing.code) {
          print("Spoofing detected");
        } else if (error.code == KeylessErrorCase.timeout.code) {
          print("The operation timed out");
        } else if (error.code == KeylessErrorCase.userCancelled.code) {
          print("The user cancelled the operation");
        } else if (error.code == KeylessErrorCase.noNetworkConnection.code) {
          print("No network connection available");
        } else if (error.code == KeylessErrorCase.deviceTampered.code) {
          print("Device security check failed");
        } else if (error.code == KeylessErrorCase.lockout.code) {
          print("Your account is temporarily locked");
        }
        break;
      default:
        // Handle internal or integration errors
        print("Authentication failed: ${error.message} (Code: ${error.code})");
        // Here you could display a generic error popup with the error code
    }
  }
}

2️⃣ Enrollment

Enrollment is the process of registering a new user by connecting their facial biometrics to a Keyless account. During this process, a full and unobstructed view of the user's face is required.

val configuration = BiomEnrollConfig()

Keyless.enroll(
  configuration = configuration,
  onCompletion = { result ->
    when (result) {
      is Keyless.KeylessResult.Success -> Log.d("KeylessSDK ", "Enroll success - userId ${result.value.keylessId}")
      is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "Enroll failure - error code ${result.error.code}")
    }
  }
)
let configuration = BiomEnrollConfig()

Keyless.enroll(
  configuration: configuration,
  onCompletion: { result in
    switch result {
    case .success(let enrollmentSuccess):
        print("Enrollment finished successfully. UserID: \(enrollmentSuccess.keylessId)")
    case .failure(let error):
        print("Enrollment finished with error: \(error.message)
    }
  })
val configuration = EnrollmentConfiguration.builder.build()

Keyless.enroll(
  enrollmentConfiguration = configuration,
  onCompletion = { result ->
    when (result) {
      is Keyless.KeylessResult.Success -> Log.d("KeylessSDK ", "Enroll success - userId ${result.value.keylessId}")
      is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "Enroll failure - error code ${result.error.code}")
    }
  }
)
let configuration = Keyless.EnrollmentConfiguration.builder.build()

Keyless.enroll(
  enrollmentConfiguration: configuration,
  onCompletion: { result in
    switch result {
    case .success(let enrollmentSuccess):
        print("Enrollment finished successfully. UserID: \(enrollmentSuccess.keylessId)")
    case .failure(let error):
        print("Enrollment finished with error: \(error.message)
    }
  })
import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/enrollment_configuration.dart';

final configuration = BiomEnrollConfig();

try {
  final result = await Keyless.instance.enroll(configuration);
  print("Enrollment finished successfully. UserID: ${result.keylessId}");
} catch (error) {
  print("Enrollment finished with error: $error");
}

Enrollment configuration

You can configure the enrollment process with optional parameters in your BiomEnrollConfig() instance or using the builder pattern methods from the EnrollmentConfiguration builder.

public data class BiomEnrollConfig(
    public val cameraDelaySeconds: Int = 2,
    public val customSecret: String?,
    public val jwtSigningInfo: JwtSigningInfo?,
    public val livenessConfiguration: LivenessSettings.LivenessConfiguration = PASSIVE_STANDALONE_HIGH,
    public val livenessEnvironmentAware: Boolean = true,
    public val operationInfo: OperationInfo?,
    public val shouldRetrieveTemporaryState: Boolean = false,
    public val shouldRetrieveEnrollmentFrame: Boolean = false,
    public val temporaryState: String?,
    public val showSuccessFeedback: Boolean = true,
    public val showInstructionsScreen: Boolean = true,
    public val showFailureFeedback: Boolean = true
)
public struct BiomEnrollConfig {
    public let cameraDelaySeconds: Int
    public let customSecret: String?
    public let jwtSigningInfo: JwtSigningInfo?
    public let livenessConfiguration: Keyless.LivenessConfiguration
    public let livenessEnvironmentAware: Bool
    public let operationInfo: Keyless.OperationInfo?
    public let shouldRetrieveTemporaryState: Bool
    public let shouldReturnEnrollmentFrame: Bool
    public let temporaryState: String?,
    public let showSuccessFeedback: Bool,
    public let showInstructionsScreen: Bool,
    public let showFailureFeedback: Bool
}
public interface EnrollmentConfigurationBuilder {

    public fun retrievingBackup(): EnrollmentConfigurationBuilder

    public fun retrievingTemporaryState(): EnrollmentConfigurationBuilder

    public fun savingSecret(customSecret: String): EnrollmentConfigurationBuilder

    public fun withBackup(backupKey: ByteArray, backupData: ByteArray): EnrollmentConfigurationBuilder

    public fun withDelay(cameraDelaySeconds: Int): EnrollmentConfigurationBuilder

    public fun withEnrollmentSelfie(): EnrollmentConfigurationBuilder

    public fun withLivenessSettings(
        livenessConfiguration: LivenessSettings.LivenessConfiguration,
        livenessTimeout: Int
    ): EnrollmentConfigurationBuilder

    public fun withOperationInfo(
        operationId: String,
        payload: String? = null,
        externalUserId: String? = null
    ): EnrollmentConfigurationBuilder

    public fun withPin(pin: String): EnrollmentConfigurationBuilder

    public fun withTemporaryState(temporaryState: String): EnrollmentConfigurationBuilder

    public fun build(): EnrollmentConfiguration
}
public protocol EnrollmentConfigurationBuilder {

    public func retrievingBackup() -> EnrollmentConfigurationBuilder

    public func retrievingTemporaryState() -> EnrollmentConfigurationBuilder

    public func savingSecret(_ customSecret: String) -> EnrollmentConfigurationBuilder

    public func withBackup(_ backup: Keyless.Backup) -> EnrollmentConfigurationBuilder

    public func withDelay(seconds: Int) -> EnrollmentConfigurationBuilder

    public func withEnrollmentSelfie() -> EnrollmentConfigurationBuilder

    public func withLivenessSettings(
        livenessConfiguration: Keyless.LivenessConfiguration,
        livenessTimeout: Int
    ) -> EnrollmentConfigurationBuilder

    public func withOperationInfo(
        id: String,
        payload: String?,
        externalUserId: String?
    ) -> EnrollmentConfigurationBuilder

    public func withPin(_ pin: String) -> EnrollmentConfigurationBuilder

    public func withTemporaryState(_ temporaryState: String) -> EnrollmentConfigurationBuilder

    public func build() -> Keyless.EnrollmentConfiguration
}
class BiomEnrollConfig {
    final String? customSecret;
    final String? temporaryState;
    final OperationInfo? operationInfo;
    final LivenessConfiguration? livenessConfiguration;
    final int? livenessTimeout;
    final bool? shouldRetrieveTemporaryState;
    final int? cameraDelaySeconds;
    final JwtSigningInfo? jwtSigningInfo;
    final DynamicLinkingInfo? dynamicLinkingInfo;
    final bool? showScreenInstructions;
    final bool? showScreenSuccessFlow;
}

Enrollment success result

Depending on the builder methods you enable, Keyless will populate the corresponding fields in the EnrollmentSuccess result reported below.

data class EnrollmentSuccess(
    val keylessId: String,
    val customSecret: String = "",
    val signedJwt: String? = null,
    val enrollmentFrame: Bitmap? = null,
    val temporaryState: String? = null
) : KeylessSdkSuccess()
public struct EnrollmentSuccess {
    public let keylessId: String?
    public let customSecret: String?
    public let enrollmentFrame: CGImage?
    public let signedJwt: String?
    public let temporaryState: String?
}
class EnrollmentSuccess {
    final String keylessId;
    final String? customSecret;
    final String? signedJwt;
    final String? temporaryState;
}

Camera Delay

Use cameraDelaySeconds to specify the delay (in seconds) between when the camera preview appears, and when the liveness processing starts.

Custom secret

During enrollment you can specify a custom secret to be saved and encrypted along with the user's biometric data using savingSecret paramter. The custom secret can be anything you can save as an ASCII string, such as a secret that you have provided to the app from the backend, the seed of an OTP protocol, or anything else.

JWT Signing info

You can specify a payload to be added to a JWT signed by Keyless with the jwtSigningInfo parameter, more in JWT signing.

Liveness Settings

Using livenessConfiguration you can configure the liveness security level during enrollment. The possible liveness configuration are under LivenessSettings.LivenessConfiguration :

PASSIVE_STANDALONE_MEDIUM
PASSIVE_STANDALONE_HIGH        //recommended configuration
PASSIVE_STANDALONE_HIGHEST

You can also specify a livenessEnvironmentAware that is by default se to true to enhance liveness detection. This parameters helps to ensure the user is in a suitable setting for verification.

More details on liveness in the dedicated liveness settings section.

Operation info

The parameter operationInfo specifies a customizable unique operation identifier and associated payload stored on the Keyless backend if the enrollment succeeds. Use this to add an extra level of confirmation in your operations.

Details on how to query our backend for stored operations are available on Operations API.

Temporary State

Keyless users can be enrolled via IDV-Bridge, Identity Verification Bridge. As a result of IDV-Bridge enrollment you receive a temporary state useful to register users in your app without undergoing the full enrollment flow.

Use the temporaryState parameter to register users from a temporary state obtained through IDV-Bridge.

You can also use a temporary state to recover an account of an existing user who lost access to the account. Follow the guide on account recovery.

Enrollment Frame

Integrators can specify whether or not to retrieve an enrollment frame, acquired during the user’s selfie capture during the enrollment or account recovery flow. You can achieve this by setting shouldReturnEnrollmentFrame to true on the BiomEnrollmentConfig (defaults to false). If the enrollment succeeds, you can retrieve the frame from the returned EnrollmentSuccess, via enrollmentFrame.

New Device Activation

This page explains how to then use the temporary state to then authenticate the user and bind the new device to then support ongoing 2 factor authentication.

Recover from temporary state

Pass the temporary state created during the enrollment flow or via IDV Bridge to recover the account on a new device. The temporary state is the one you obtained and stored securely when enrolling users via IDV Bridge or the Mobile SDK on the previous sub-page

When enrolling from the temporary state, Keyless shows the enrollment UI to users as of 5.0.1 including the live filters.


// temporaryState retrieved from previous step
val temporaryState = "<your_temporary_state>"

val enrollConfig = BiomEnrollConfig(temporaryState = temporaryState)

Keyless.enroll(
  configuration = enrollConfig,
  onCompletion = { result ->
    when (result) {
      is Keyless.KeylessResult.Success -> {

      	// account recovered
      	val userId = result.value.userId

      }
      is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "error code ${result.error.code}")
    }
  }
)

// temporaryState retrieved from previous step
let temporaryState = "<your_temporary_state>"

let enrollConfig = BiomEnrollConfig(temporaryState: temporaryState)

Keyless.enroll(
  configuration: enrollConfig,
  onCompletion: { result in
    switch result {
    case .success(let enrollSuccess):
    	
    	// account recovered
      	let userId = enrollSuccess.userId

    case .failure(let error):
        print("error code: \(error.code)
    }
  })
import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/enrollment_configuration.dart';

// temporaryState retrieved from previous step
final temporaryState = "<your_temporary_state>";

final enrollConfig = BiomEnrollConfig(temporaryState: temporaryState);

try {
    final result = await Keyless.instance.enroll(enrollConfig);
    // account recovered
    print("Account recovered successfully. UserID: ${result.keylessId}");
} catch (error) {
    print("Account recovery failed: $error");
}

The account is recovered and it's now possible to authenticate the user with ongoing 2 factor authentication with a single

3️⃣ Authentication

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

If the biometrics match, Keyless authenticates the user.

Authentication configuration

You can configure the authentication process with optional parameters in your BiomAuthConfig() instance or using the builder pattern methods from the AuthenticationConfiguration builder.

The successAnimationEnabled and later showScreenSuccessFlow field has been renamed to showSuccessFeedback, triggering a breaking change. Moreover the success animation is now shown by default.

Authentication success result

Depending on the builder methods you enable, Keyless will populate the corresponding fields in the AuthenticationSuccess result reported below.

Backup data

Backup data is no longer recommended to perform account recovery and the feature has been removed from Android and iOS SDKs. Use the temporary state instead. Follow the guide on .

Keyless can generate backup data that you can use to recover an account.

To create the backup data use the shouldRetrieveBackup configuration parameter. Once authentication succeeds, copy the backup data from the AuthenticationSuccess result, and store it securely.

To recover an account, use backup parameter during enrollment more in .

Camera Delay

Use cameraDelaySeconds to specify the delay (in seconds) between when the camera preview appears, and when the liveness processing starts.

Custom Secret

If you saved a custom secret during , you can retrieve it using the shouldRetrieveSecret parameter.

Keyless will populate the field customSecret in the AuthenticationSuccess result.

Furthermore, such a custom secret can be deleted using the shouldDeleteSecret parameter.

JWT Signing info

You can specify a payload to be added to a JWT signed by Keyless with the jwtSigningInfo parameter, more in .

Liveness Settings

Using livenessConfiguration you can configure the liveness security level during enrollment. The possible liveness configuration are under LivenessSettings.LivenessConfiguration :

You can also specify a livenessEnvironmentAware that is by default se to true to enhance liveness detection. This parameters helps to ensure the user is in a suitable setting for verification.

More details on liveness in the dedicated section.

Operation info

The parameter operationInfo specifies a customizable unique operation identifier and associated payload stored on the Keyless backend if the enrollment succeeds.

Details on how to query our backend for stored operations are available on .

Temporary State

Use the shouldRetrieveTemporaryState parameter to creata a temporary state useful for the .

Photo Enrollment

Use a photo (from an identity document) to enroll the initial biometric data of the user

As introduce in the :

enrollment is the process of registering a new user by connecting their facial biometrics to a Keyless account. During this process, a full and unobstructed view of the user's face is required.

If you possess a trusted source, such as an identity document, Keyless allows you to register a new user connecting their facial biometric from the identity document photo. The assumption behind the feature is that the identity document photo fulfills the requirement of a full and unobstructed view of the user's face.

To retrieve a document photo Keyless offers the utility.

To enroll a user from the photo use the PhotoEnrollConfig.

Photo Enrollment configuration

You can configure the enrollment process with optional parameters in your PhotoEnrollConfig() instance.

Photo Enrollment success result

If the Enroll from photo is successful you will find in the EnrollmentSuccess containing the corresponding fields you requested during configuration.

If the enrollment is successful the user is enrolled and can with Keyless from now on.

Keyless SDK Documentation

Authenticate people, not just devices

To embed privacy-preserving biometric authentication in your mobile applications:

Step 1: Understand how the Keyless components .

Step 2: Understand a typical and how it interacts with your application.

Step 3: Follow the for Android or iOS.

Next steps

  • Learn about user and device management with the .

  • Learn about user and device management with the .

Get user devices

get

Get user devices

Authorizations
Path parameters
userIdstring · Uppercase HEX stringRequired

The user id

Responses
200
User devices
application/json
400
Request is invalid and shouldn't be retried if the request is unchanged
application/json
401
Unauthorized request, the request can't be processed unless you provide a valid authentication method
application/json
404
Resource not found
application/json
406
Not acceptable representation
application/json
500
An internal error occurred, please try again later or contact the support.
application/json
get

Delete user device

delete

Delete user device

Authorizations
Path parameters
userIdstring · Uppercase HEX stringRequired

The user id

publicSigningKeystringRequired

Public signing key

Responses
200
Device successfully deleted
application/json
400
Request is invalid and shouldn't be retried if the request is unchanged
application/json
401
Unauthorized request, the request can't be processed unless you provide a valid authentication method
application/json
404
Resource not found
application/json
406
Not acceptable representation
application/json
500
An internal error occurred, please try again later or contact the support.
application/json
delete

Get a user's pending operations

get

Get a user's pending operations

Authorizations
Path parameters
userIdstring · Uppercase HEX stringRequired

The user id

Responses
200
An user's pending operations
application/json
400
Request is invalid and shouldn't be retried if the request is unchanged
application/json
401
Unauthorized request, the request can't be processed unless you provide a valid authentication method
application/json
404
Resource not found
application/json
406
Not acceptable representation
application/json
500
An internal error occurred, please try again later or contact the support.
application/json
get

Get a customer operation

get

Get a customer operation

Authorizations
Path parameters
operationIdstringRequired

Unique operation identifier set and managed by the client

Responses
200
A customer operation
application/json
400
Request is invalid and shouldn't be retried if the request is unchanged
application/json
401
Unauthorized request, the request can't be processed unless you provide a valid authentication method
application/json
404
Resource not found
application/json
406
Not acceptable representation
application/json
500
An internal error occurred, please try again later or contact the support.
application/json
get

Create a pending operation

post

Create a pending operation

Authorizations
Path parameters
userIdstring · Uppercase HEX stringRequired

The user id

Body
externalUserIdstringOptional

User id set and managed by the client

operationIdstringRequired

Unique operation identifier set and managed by the client

operationPayloadstringOptional

Operation payload set and managed by the client

Responses
200
Operation created successfully
application/json
400
Request is invalid and shouldn't be retried if the request is unchanged
application/json
401
Unauthorized request, the request can't be processed unless you provide a valid authentication method
application/json
404
Resource not found
application/json
406
Not acceptable representation
application/json
409
The requested operation conflicts with the current state of the server
application/json
415
Media type not supported, the request can't be processed
application/json
500
An internal error occurred, please try again later or contact the support.
application/json
post

Get the customer public key

get

Get the customer public key in PEM format

Authorizations
Responses
200
The public key in PEM format
application/json
400
Request is invalid and shouldn't be retried if the request is unchanged
application/json
401
Unauthorized request, the request can't be processed unless you provide a valid authentication method
application/json
406
Not acceptable representation
application/json
get

Verify a signed JWT message

post

Verify a signed JWT message. The only format accepted for now is a JWT generated by a PIN signature

Authorizations
Body
messagestring · JWTRequired

The signed JWT message

Responses
200
The verification completed successfully. Check the result to see if the signature is valid or not
application/json
400
Request is invalid and shouldn't be retried if the request is unchanged
application/json
401
Unauthorized request, the request can't be processed unless you provide a valid authentication method
application/json
406
Not acceptable representation
application/json
415
Media type not supported, the request can't be processed
application/json
post

Delete an user

delete

Delete an user and all associated entities

Authorizations
Path parameters
userIdstring · Uppercase HEX stringRequired

The user id

Responses
200
User successfully deleted
application/json
400
Request is invalid and shouldn't be retried if the request is unchanged
application/json
401
Unauthorized request, the request can't be processed unless you provide a valid authentication method
application/json
404
Resource not found
application/json
406
Not acceptable representation
application/json
500
An internal error occurred, please try again later or contact the support.
application/json
delete
GET /v2/users/{userId}/devices HTTP/1.1
Host: 
X-Api-Key: YOUR_API_KEY
Accept: */*
[
  {
    "userId": "0123456789ABCDEF",
    "sdkCustomerId": 1,
    "publicSigningKey": "text",
    "publicEncryptionKey": "text",
    "state": "ACTIVE",
    "createdAt": "2020-01-02T03:04:05.242194378",
    "osVersion": "text",
    "sdkVersion": "text",
    "deletedAt": "2020-01-02T03:04:05.242194378"
  }
]
DELETE /v2/users/{userId}/devices/{publicSigningKey} HTTP/1.1
Host: 
X-Api-Key: YOUR_API_KEY
Accept: */*
{
  "success": true
}
GET /v2/users/{userId}/operations/pending HTTP/1.1
Host: 
X-Api-Key: YOUR_API_KEY
Accept: */*
[
  {
    "operationId": "123456abcdef",
    "operationPayload": "payload",
    "externalUserId": "abcdef123456",
    "authType": "biom"
  },
  {
    "operationId": "654321fedcba"
  }
]
GET /v2/operations/{operationId} HTTP/1.1
Host: 
X-Api-Key: YOUR_API_KEY
Accept: */*
{
  "id": 1,
  "timestamp": "2020-01-02T03:04:05.68",
  "nodeId": "text",
  "userId": "0123456789ABCDEF",
  "apiKey": "text",
  "externalUserId": "text",
  "operationId": "text",
  "operationPayload": "text",
  "result": true,
  "authType": "biom",
  "state": "approved"
}
POST /v2/users/{userId}/operations HTTP/1.1
Host: 
X-Api-Key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 72

{
  "externalUserId": "text",
  "operationId": "text",
  "operationPayload": "text"
}
{
  "success": true
}
GET /v2/verify-jwt/public-key HTTP/1.1
Host: 
X-Api-Key: YOUR_API_KEY
Accept: */*
{
  "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwJ1\n-----END PUBLIC KEY-----"
}
POST /v2/verify-jwt HTTP/1.1
Host: 
X-Api-Key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 18

{
  "message": "text"
}
{
  "result": true
}
DELETE /v2/users/{userId} HTTP/1.1
Host: 
X-Api-Key: YOUR_API_KEY
Accept: */*
{
  "success": true
}
interact with your application server
biometric authentication flow
getting started guide
Admin portal
RESTful API
val configuration = AuthenticationConfiguration.builder.build()

Keyless.authenticate(
    authenticationConfiguration = configuration,
    onCompletion = { result ->
        when (result) {
            is Keyless.KeylessResult.Success -> Log.d("KeylessSDK ", "Authentication success")
            is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "Authentication failure - error code ${result.error.code}")
        }
    }
)
let configuration = Keyless.AuthenticationConfiguration.builder.build()

Keyless.authenticate(
    authenticationConfiguration: configuration,
    onCompletion: { result in
        switch result {
        case .success(let success):
            print("Authentication success")
        case .failure(let error):
            break
        }
    })
import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/authentication_configuration.dart';

final configuration = BiomAuthConfig();

try {
  final result = await Keyless.instance.authenticate(configuration);
  print("Authentication success");
} catch (error) {
  print("Authentication failure");
}
interface AuthenticationConfigurationBuilder {

    fun retrievingBackup(): AuthenticationConfigurationBuilder

    fun retrievingSecret(): AuthenticationConfigurationBuilder

    fun deletingSecret(): AuthenticationConfigurationBuilder

    fun retrievingTemporaryState(): AuthenticationConfigurationBuilder

    fun withDelay(cameraDelaySeconds: Int): AuthenticationConfigurationBuilder

     fun withLivenessSettings(
        livenessConfiguration: LivenessSettings.LivenessConfiguration,
        livenessTimeout: Int
    ): AuthenticationConfigurationBuilder

    fun withMessageToSign(message: String): AuthenticationConfigurationBuilder

    fun withOperationInfo(
        operationId: String,
        payload: String? = null,
        externalUserId: String? = null
    ): AuthenticationConfigurationBuilder

    fun withPin(pin: String): AuthenticationConfigurationBuilder

    fun withSuccessAnimation(enabled: Boolean = true): AuthenticationConfigurationBuilder

    fun build(): AuthenticationConfiguration
}
public class Builder {

    public func retrievingBackup() -> Builder

    public func retrievingSecret() -> Builder

    public func deletingSecret() -> Builder

    public func retrievingTemporaryState() -> Builder

    public func revokingDevice(id: String) -> Builder

    public func withDelay(seconds: Int) -> Builder

    public func withLivenessSettings(
        livenessConfiguration: LivenessConfiguration,
        livenessTimeout: Int
    ) -> Builder

    public func withMessageToSign(_ message: String) -> Builder

    public func withOperationInfo(
        id: String,
        payload: String? = nil,
        externalUserId: String? = nil
    ) -> Builder

    public func withPin(_ pin: String) -> Builder

    public func withSuccessAnimation(_ enabled: Bool) -> Builder

    public func build() -> AuthenticationConfiguration
}
class BiomAuthConfig extends AuthConfig {
    final LivenessConfiguration? livenessConfiguration;
    final int? livenessTimeout;
    final int? cameraDelaySeconds;
    final bool? shouldRetrieveTemporaryState;
    final String? b64NewDeviceData;
    final String? b64OldDeviceData;
    final String? deviceToRevoke;
    final bool? shouldRetrieveSecret;
    final bool? shouldRemovePin;
    final JwtSigningInfo? jwtSigningInfo;
    final DynamicLinkingInfo? dynamicLinkingInfo;
    final OperationInfo? operationInfo;
    final bool? showScreenSuccessFlow;
}
data class AuthenticationSuccess(
    val customSecret: String? = null,
    val signedJwt: String? = null,
    val temporaryState: String? = null
) : KeylessSdkSuccess()
public struct AuthenticationSuccess {
    public let customSecret: String?
    public let signedJwt: String?
    public let temporaryState: String?
}
class AuthenticationSuccess {
    final String? customSecret;
    final String? signedJwt;
    final String? temporaryState;
}
PASSIVE_STANDALONE_MEDIUM
PASSIVE_STANDALONE_HIGH        //recommended configuration
PASSIVE_STANDALONE_HIGHEST
enrollment
account recovery
backup
enrollment
JWT signing
liveness settings
Operations API
account recovery
val configuration = BiomAuthConfig()

Keyless.authenticate(
    configuration = configuration,
    onCompletion = { result ->
        when (result) {
            is Keyless.KeylessResult.Success -> Log.d("KeylessSDK ", "Authentication success")
            is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "Authentication failure - error code ${result.error.code}")
        }
    }
)
let configuration = BiomAuthConfig()

Keyless.authenticate(
    configuration: configuration,
    onCompletion: { result in
        switch result {
        case .success(let success):
            print("Authentication success")
        case .failure(let error):
            break
        }
    })
public data class BiomAuthConfig(
    public val cameraDelaySeconds: Int = 0,
    public val jwtSigningInfo: JwtSigningInfo?,
    public val livenessConfiguration: LivenessSettings.LivenessConfiguration = PASSIVE_STANDALONE_HIGH,
    public val livenessEnvironmentAware: Boolean = true
    public val operationInfo: OperationInfo?,
    public val shouldRemovePin: Boolean = false,
    public val shouldRetrieveTemporaryState: Boolean = false,
    public val shouldRetrieveSecret: Boolean = false,
    public val shouldDeleteSecret: Boolean = false,
    public val showSuccessFeedback: Boolean = true
)
public struct BiomAuthConfig: AuthConfig {
    public let cameraDelaySeconds: Int
    public let jwtSigningInfo: JwtSigningInfo?
    public let livenessConfiguration: Keyless.LivenessConfiguration
    public let livenessEnvironmentAware: Bool
    public let operationInfo: Keyless.OperationInfo?
    public let shouldRemovePin: Bool
    public let shouldRetrieveTemporaryState: Bool
    public let shouldRetrieveSecret: Bool
    public let shouldDeleteSecret: Bool
    public let showSuccessFeedback: Bool
}
import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/enrollment_configuration.dart';

// documentImage from eDocument.
final configuration = PhotoEnrollConfig(documentImage: documentImage);

try {
  final result = await Keyless.instance.enroll(configuration);
  print("Enrollment finished successfully. UserID: ${result.keylessId}");
} catch (error) {
  print("Enrollment finished with error: $error");
}
public data class PhotoEnrollConfig(
    public val photo: Bitmap,
    public val temporaryState: String? = null,
    public val operationInfo: OperationInfo? = null,
    public val jwtSigningInfo: JwtSigningInfo? = null
)
public struct PhotoEnrollConfig{
    public let photo: CGImage
    public let operationInfo: Keyless.OperationInfo?
    public let jwtSigningInfo: JwtSigningInfo?
    public let temporaryState: String?
}
class PhotoEnrollConfig{
  final Uint8List? documentImage;
  final String? temporaryState;
  final OperationInfo? operationInfo;
  final JwtSigningInfo? jwtSigningInfo;
}
data class EnrollmentSuccess(
    val signedJwt: String? = null,
) : KeylessSdkSuccess()
public struct EnrollmentSuccess {
    public let signedJwt: String?
}
class EnrollmentSuccess {
    final String? signedJwt;
}
enrollment section
Keyless Mobile Document SDK
authenticate
// photoBitmap is the bitmap you created from the document photo.
val configuration = PhotoEnrollConfig(photo = photoBitmap)

Keyless.enroll(
  configuration = configuration,
  onCompletion = { result ->
    when (result) {
      is Keyless.KeylessResult.Success -> Log.d("KeylessSDK ", "Enroll success - userId ${result.value.keylessId}")
      is Keyless.KeylessResult.Failure -> Log.d("KeylessSDK ", "Enroll failure - error code ${result.error.code}")
    }
  }
)
// photoUIImage is the UIImage you created from the document photo.
let configuration = PhotoEnrollConfig(photo: photoUIImage)

Keyless.enroll(
  configuration: configuration,
  onCompletion: { result in
    switch result {
    case .success(let enrollmentSuccess):
        print("Enrollment finished successfully. UserID: \(enrollmentSuccess.keylessId)")
    case .failure(let error):
        print("Enrollment finished with error: \(error.message)
    }
  })

5️⃣ Backup

Backup data is no longer the recommended approach to perform account recovery, and the feature has been removed from Android and iOS SDKs. The recommended flow is using the temporary state. Follow the guide on account recovery.

Back up your user's data so you can restore their Keyless account when necessary.

Create a backup (not recommended)

Create backups after authentication, and push them into your backup system. Each backup is encrypted with aes-gcm using the backupKey as symmetric key. You need both backupData and backupKey to restore an account with Keyless.

import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/authentication_configuration.dart';

// Retrieve temporary state during authentication
final configuration = BiomAuthConfig(shouldRetrieveTemporaryState: true);

try {
    final result = await Keyless.instance.authenticate(configuration);
    if (result.temporaryState != null) {
        print("Temporary state retrieved successfully");
        // Store temporaryState securely
    }
} catch (error) {
    print("Failed to retrieve temporary state: $error");
}

Restore from a backup (not recommended)

Restore the user account by providing backupData and backupKey to the withBackup method:

import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/enrollment_configuration.dart';

// Create enrollment configuration with backup
final configuration = BiomEnrollConfig(
    backup: KeylessBackup(
        data: backupData,
        key: backupKey
    )
);

try {
    final result = await Keyless.instance.enroll(configuration);
    print("Enrollment from backup successful");
} catch (error) {
    print("Enrollment from backup failed: $error");
}

Sample Apps

A description of, and links to, the Keyless sample apps to support integrators with a working example.

Keyless has created sample apps in both Android and iOS as companions to the official documentation. This effectively shows our SDKs working inside a basic application that can perform both Enrollment and Authentication flows.

This supports:

  • Developers who want to integrate and use the Keyless Mobile SDK in their apps, by allowing them to see the SDK functioning inside a working app or emulator.

  • A working demonstration of our SDK to see it in action.

The below links will launch the relevant github pages in a new tab:

  • Android sample app

  • iOS sample app

Dynamic Linking

You can leverage the Keyless authentication mechanism to sign unrelated transactions, including Strong customer authentication (SCA) transactions.

Payment service providers compliant with are required to:

  • Generate an authentication code specific to the amount of the payment transaction and the payee agreed to by the payer when initiating the transaction

  • Make the payer aware of the amount of the payment transaction, and of the payee

Keyless helps you by:

  • protecting the authentication code that you use for dynamic linking.

  • displaying and signing the information to make the payer aware of details of the transaction.

Keyless is not a payment service provider. Keyless won't issue an authentication code tied to the transaction information.

Strong customer authentication (SCA)

By adding Keyless to your checkout flow you also benefit from .

SCA requires authentication to use at least two of the following three elements.

  • Something that only the customer knows. For example, a password or PIN.

  • Something that only the customer has. For example, a mobile phone or hardware token.

  • Something that the customer is. For example, a biometric such as a fingerprint or face.

With Keyless Passwordless MFA you can satisfy the last two points from the list above.

SCA Compliant Dynamic Linking

The following sections contain some examples on implementing SCA with Keyless.

Display transaction information

Keyless displays a screen containing a list of labels and associated information on your behalf. For this reason, the format of the dynamicLinkingInfo must be a jsonArray containing jsonObjects (key/value pairs). We expect a valid JSON as follows:

This information is added to the Authentication request that the user needs to approve.

SCA tied to the authentication code

Once the user approves the transaction data, Keyless starts the authentication to:

  1. Authenticate the payer with the device factor and the biometric factor using Keyless MFA.

  2. Tie the transaction data to the Keyless MFA

To tie the transaction data to the Keyless MFA, populate the parameter dynamicLinkingInfo of the authentication configuration AuthConfig. Add the authentication code or any other information you want to display to the user and sign with Keyless MFA. For example, add the "authentication code".

The transactionData contained in dynamicLinkingInfo must respect the format to .

Keyless can produce a signed JWT containing a claim titled td (transaction data) that contains the payload you passed as dynamicLinnkingInfo.

Keyless is not storing history of records about the transaction amount, the payee, the payer and the authentication code.

Verify the transaction

If the authentication is successful, the AuthenticationSuccess contains the following fields:

  • signedJwt: the signed JWT (specs below).

Verify the JWT using the public key from .

Congrats, you just performed a Strong Customer Authentication displaying and signing the transaction information.

Getting Started

This API allows for backend-to-backend communication between your backend and the Keyless servers.

The server API has end points for automating aspects of your Keyless setup:

  • List and revoke

  • Remove

  • Perform extra backend to backend security

API Access

  • All API endpoints require the X-Api-Key: <SECRET_API_KEY> header with your security key

  • All API endpoints are available at https://api.keyless.io

devices
users
operations
[
    {key1 : value1},
    {key2 : value2},
    ...
    {keyN: valueN}
]
//Keyless adds a td claim to the JWTs containing the data you specify
val dynamicLinkingInfo = DynamicLinkingInfo(transactionData = "<your transaction data to display and sign>")

// authenticate with biometric
val biomAuthConfig = BiomAuthConfig(dynamicLinkingInfo = dynamicLinkingInfo)

// perform the authentication
Keyless.authenticate(
    configuration = biomAuthConfig,
    onCompletion = { /*TODO: process result*/ }
)
//Keyless adds a td claim to the JWTs containing the data you specify
let dynamicLinkingInfo = DynamicLinkingInfo(transactionData = "<your transaction data to display and sign>")

// authenticate with biometric
let biomAuthConfig = BiomAuthConfig(dynamicLinkingInfo: dynamicLinkingInfo)

// perform the authentication
Keyless.authenticate(
    configuration: biomAuthConfig,
    onCompletion: { /*TODO: process result*/ }
)
import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/authentication_configuration.dart';
import 'package:keyless_flutter_sdk/models/dynamic_linking_info.dart';

final dynamicLinkingInfo = DynamicLinkingInfo(transactionData: "<your transaction data to display and sign>");

// authenticate with biometric
final biomAuthConfig = BiomAuthConfig(dynamicLinkingInfo: dynamicLinkingInfo);

try {
    // perform the authentication
    final result = await Keyless.instance.authenticate(biomAuthConfig);
    // process the signed JWT from result.signedJwt
    print("Authentication successful. Signed JWT: ${result.signedJwt}");
} catch (error) {
    print("Authentication failed: $error");
}
// JWT header
{
  "alg": "ES256",
  "typ": "JWT",
  "kid": "PIN/FACE"
}
// JWT payload
{
  "iat": 1720519812,
  "td": "your transaction data to display and sign",
  "version": "1.1.0",
  "sub": "keyless_id"
}
SCA PSD2 dynamic linking
Keyless Passwordless Multi Factor Authentication (MFA)
display transaction information
Keyless backend

Changelog

For a seamless SDK integration make sure to follow the section getting started.

You can check all the prerequisites that your app must meet for a successsful SDK integration under the prerequisites section.

Please note releases marked as release candidate - rc are available to selected customers in advance of the official release via partners-rc cloudsmith repo. Please note regression tests and QA activity is incomplete and we strongly advise against shipping these versions into production environments.


5.1.0 - release candidate

Highlights

  • Enrollment Frame: Integrators can specify whether or not to retrieve an enrollment frame, acquired during the user’s selfie capture during the enrollment or account recovery flow. See details in Enrollment Frame section.

  • Enhanced Anti Inject: this release introduces a variant for enhanced frames injection prevention available to selected customers.


5.0.5 iOS - 5.0.6 Android

Highlights

  • Environment-Aware Liveness Detection: a new, optional check has been introduced to enhance liveness detection helping to ensure the user is in a suitable setting for verification. This feature is enabled by default. To disable it, set livenessEnvironmentAware = false in the configuration for enrollment, authentication, or de-enrollment. See details in Liveness Settings section.

  • Face Occlusion Detection for Enrollment: during the enrollment process, the SDK now provides immediate feedback to the user if their face is partially covered. A message, "Make sure your face is clearly visible," will be displayed to guide them..

Fixes

  • Improved Error Handling for Temporary States: the SDK now returns a more specific internal error (error code: 539) if the maximum number of temporary states is reached during an operation.

  • Added a Cancellation Button to the Camera View: an "x" button has been added to the top-left corner of the camera screen during enrollment, allowing users to safely cancel the face scan process.


5.0.3

Highlights

  • Enroll from photo preview: This feature introduces the ability to enroll users using a photo provided from a trusted source.

    • Developer Responsibility: You are responsible for ensuring that the photo originates from a verified and trusted source (e.g., an official identity document).

    • Security Note: Exposing this functionality to your end-users without rigorous verification of the photo's origin may introduce vulnerabilities, such as a user enrolling with another individual's facial image. Please implement appropriate safeguards.

Enhancement: biometric processing

  • Improved biometric performance through optimizations in frame timestamp processing.


5.0.1

Highlights

  • New Enrollment UX: a freshened up, new User Interface has been implemented and made available.

    • It now displays real-time feedback about the quality of the processed image, allowing users to address the issues based on the messages shown during face capture.

      • Note these messages are not customizable since they are tied to Keyless image processing.

    • The temporary state now displays the enrollment UI. The change means that users will experience an onboarding flow as for plain enrollment (withtout temporary state).

      • This change was made based on customer feedback in recognition of the fact that some users will be experiencing Keyless for the first time, specifically if they were enrolled via IDV Bridge.

    • The UI differences are reported in the tab SDK v5 from the UI customization section.

  • Fixes in this release

    • Added showScreenFailureFlow for consistency with the happy path showScreenSuccessFlow. By default the SDK shows an error screen for unhappy paths but the caller app can opt-out from this default behavior.

    • Return an error if camera permission is not granted. Keyless prompts the user for camera permission and now also informs the caller app that permission was not granted returning the error 30009 - camera denied .

      • See user errors for details of this and all other errors.

Improved error handling and information

Integrators familiar with our error handling documentation will notice that we have:

  • Added more detailed insight and guidance for the different types of errors and the relevant error code ranges for integrators to be aware of.

  • More clearly laid out the different types of User Errors with their codes & descriptions.

  • "Rejected" error - we have split out instances where Liveness could not be established into a new "Rejected" error. Previously these were all labelled as "Spoofing" errors, however we recognized that in some instances these incorrectly added a framed the authentication attempt as more malicious than was fair. Please note, the guidance remains that all modelling is in some way predictive and thefore we would still advise that "Spoofing" does not guarantee that there was malicious intent and for various reasons it's still better to assume positive intent in how you handle these with users.

SDK Size Reduction

Following customer feedback, we have reduced the size of the biometric libraries which has reduced both the iOS and Android SDK integration and download size. Please note that integration size can vary significantly between customers so please contact us if you have questions in this area.

Support for iOS emulator

The Keyless iOS SDK can now be run on an iOS emulator on both Windows PC or Mac, allowing integrators to achieve faster develop and test cycles as they make changes.

Deprecations

  • Liveness timeout no longer has effect and will be removed in future releases.

    • This change was made given a set of comprehensive improvements to the Liveness biometrics models. We plan to support configuration options to support both faster and slower authentication and enrollment experiences in upcoming releases.

  • The mobile SDK Lockout policy, which had some reported inconsistencies, is now managed on the backend so no longer has an effect and will be removed in future releases.

    • This change was implemented based on customer feedback and done in conjunction with other changes:

      • Client side errors (example Liveness failures) now are sent to the server side and will impact the server side policy.

      • The server side policy can now be configured per customer tenant (max failed attempts, time period, suspension period). Speak to the Keyless team if you would like to review and change your lockout policy.

Breaking changes

  • With the new UI texts that are shown to the user have changed. If you are using text customization, please make sure to update to the identifiers from the tab SDK v5 text section.

  • The API showScreenSuccessFaceCapture is no longer available since enrollment no longer considers the successful face capture step.

  • The successAnimationEnabled and later showScreenSuccessFlow field has been renamed to showSuccessFeedback.

  • The showScreenFailureFlow field has been renamed to showFailureFeedback.

  • The showScreenInstructions field has been renamed to showInstructionsScreen.

  • The enrollment progress onProgress callback is no longer available since progress is not shown with the updated UX.


4.8.2 iOS - 4.8.3 Android

Highlights

  • Improved biometric performance.

  • Improved error handling and information - docs .

  • SDK Size Reduction.

  • Support for iOS emulator.

  • Fix (Android): addressed biometric performance on Pixel devices.

  • Fix (Android): on premise remove validate device before authenticate

Improved error handling and information

  • Introduced a new "Rejected" error - we have split out instances where Liveness could not be established into a new "Rejected" error. Previously these were all labelled as "Spoofing" errors, however we recognized that in some instances these incorrectly added a framed the authentication attempt as more malicious than was fair. Please note, the guidance remains that all modelling is in some way predictive and thefore we would still advise that "Spoofing" does not guarantee that there was malicious intent and for various reasons it's still better to assume positive intent in how you handle these with users.

SDK Size Reduction

Following customer feedback, we have reduced the size of the biometric libraries which has reduced both the iOS and Android SDK integration and download size. Please note that integration size can vary significantly between customers so please contact us if you have questions in this area.

Support for iOS emulator

The Keyless iOS SDK can now be run on an iOS emulator on both Windows PC or Mac, allowing integrators to achieve faster develop and test cycles as they make changes.


4.7.5 iOS - 4.7.6 Android

Highlights

  • Fix: fix client state retro compatibility with version >2.0.0 of the Keyless Agent


4.7.4

Highlights

  • UX: optional screens - it is now possible to opt-out the optional screens you don’t wish to show - docs

  • Enhancement: query remaining lockout time - docs\

UX: optional screens

We understand that the experience our customers create for their end users is of the utmost importance. To provide greater flexibility, we’ve made certain screens and steps in the Authentication and Enrollment flows optional. These screens will remain visible by default, but you now have the ability to opt out of displaying those that are not relevant to your workflow. For more details, please refer to the documentation.

Please note that the camera view and Step 2 (“Enrollment Progress”) will remain mandatory for now, as they are critical to ensuring liveness detection and security during the capture process. However, Step 2 is highly customizable in terms of text and color scheme to align with your branding.

In upcoming releases, we will also be adding sample code and “User Flow” guides to further empower integrators and designers in tailoring their user experience.

Enhancement: query remaining lockout time

Customers can configure the maximum number of errors a user can make before being locked out for a 10-minute period.

Based on customer feedback, we’ve enhanced this feature to allow querying the remaining lockout time (in seconds) after a user has been locked out. For implementation details, please refer to the documentation.

This enhancement enables customers to display the remaining lockout time to users at any given moment, improving transparency and user experience.

Bugs and Fixes

  • Fix (Android): Resolved a compatibility issue between the Compose Material library and Flutter.


4.7.3

  • UX: SDK Theme is customizable by customer - docs

  • Feature: possibility to specify a path to Keyless artifacts. Artifacts are provided by Keyless by default. Artifacts path can be overridden by the customer.


4.7.2

  • Feature: improved PSD2 compliance - docs

  • Feature: account recovery - docs

  • Fix: remove lottie dependency

  • UX: remove success animation in favor of static image


4.7.0

  • API Surface: deprecate builder pattern in favor of optional config constructor parameters (builders are deprecated)

  • Feature: expose SDK logs to customer app - docs

  • Feature: expose JWT signature compatible with core backend key pairs - docs


4.6.7

  • Bugfix: internal data cleanup

  • Performance improvements: avoid unnecessary assets extraction


4.6.6

Feature: Shared Circuits: integrators can now set the desired number of shared circuits when calling an sdk configure Fix: Liveness: update liveness setting for higher security


4.6.5

  • Feature: enroll with Keyless from an Auth0 user’s IdToken


4.6.3

  • Dynamic linking feature: new API exposing interface to choose one authentication method (BiomAuthConfig | PinAuthConfig)

  • UI customization: it is now possible to customize the font for Keyless SDK screens

  • UI customization: it is now possible to customize the brand color (primary accent color) for Keyless SDK screens


PIN authentication

Use a PIN to perform specific operations when biometric authentication is not necessary

For authentication scenarios that don't necessitate biometric recognition, you can use PIN as an alternative.

PIN authentication has limited capabilities compared to biometric authentication. PIN supports:

  • ,

  • ,

Keyless requires at least one of the following authentication factor to be present for each user:

  • biometric factor

  • PIN factor

PIN factor can be any valid String. Numbers are not enforced but are recommended, given the familiarity of numeric PINs for end users.

Enrollment with PIN

To enroll using the PIN factor create the following configuration:

To enroll multiple authentication factors you need call Keyless.enroll for each factor.

Authentication with PIN

To authenticate using the PIN factor create the following configuration:

De-enrollment with PIN

Note that de-enrolling deletes the user biometric factor as well as the PIN factor. If you just want to remove the PIN authentication factor, use the instead.

To de-enroll using the PIN authentication factor, create the following configuration:

Remove PIN (keeping biometric factor)

To remove the PIN factor, while still keeping the biometric factor, perform a biometric authentication using the following configuration:

PIN utilities

To change the PIN use the newPin parameter in the PinAuthConfig:

To remove the PIN factor and keep the user enrolled with the biometric factor use the shouldRemovePin parameter in PinAuthConfig:

final configuration = PinEnrollConfig(
    pin: "myPin"
);

try {
    await Keyless.instance.enroll(configuration);
    print("Enrollment successful");
} catch (error) {
    print("Enrollment failed: $error");
}
val configuration = PinAuthConfig(pin = "1234")

Keyless.authenticate(
  configuration = configuration,
  onCompletion = { /*TODO: process result*/  }
)
let configuration =  PinAuthConfig(pin: "1234")

Keyless.authenticate(
  configuration: configuration,
  onCompletion: { /*TODO: process result*/  }
)
val configuration = AuthenticationConfiguration.builder
  .withPin("1234")
  .build()

Keyless.authenticate(
  configuration = configuration,
  onCompletion = { /*TODO: process result*/  }
)
let configuration = Keyless.AuthenticationConfiguration.builder
  .withPin("1234")
  .build()

Keyless.authenticate(
  configuration: configuration,
  onCompletion: { /*TODO: process result*/  }
)
final configuration = PinAuthConfig(
    pin: "1234"
);

try {
    final result = await Keyless.instance.authenticate(configuration);
    print("Authentication successful");
} catch (error) {
    print("Authentication failed: $error");
}
val configuration = PinDeEnrollConfig(pin = "1234")

Keyless.deEnroll(
  configuration = configuration,
  onCompletion = { /*TODO: process result*/  }
)
let configuration =  PinDeEnrollConfig(pin: "1234")

Keyless.deEnroll(
  configuration: configuration,
  onCompletion: { /*TODO: process result*/  }
)
val configuration =
DeEnrollmentConfiguration.builder
  .withPin("myPin")
  .build()

Keyless.deEnroll(
  configuration = configuration,
  onCompletion = { /*TODO: process result*/  }
)
let configuration =
Keyless.DeEnrollmentConfiguration.builder
  .withPin("myPin")
  .build()

Keyless.deEnroll(
  configuration: configuration,
  onCompletion: { /*TODO: process result*/  }
)
final configuration = PinDeEnrollConfig(
    pin: "1234"
);

try {
    await Keyless.instance.deEnroll(configuration);
    print("De-enrollment successful");
} catch (error) {
    print("De-enrollment failed: $error");
}
val configuration = BiomAuthConfig(shouldRemovePin = true)

Keyless.authenticate(
  configuration = configuration,
  onCompletion = { /*TODO: process result*/  }
)
let configuration =  BiomAuthConfig(shouldRemovePin: true)

Keyless.authenticate(
  configuration: configuration,
  onCompletion: { /*TODO: process result*/  }
)
val configuration =
AuthenticationConfiguration.builder
  .removingPin()
  .build()

Keyless.authenticate(
  configuration = configuration,
  onCompletion = { /*TODO: process result*/  }
)
let configuration =
Keyless.AuthenticationConfiguration.builder
  .removingPin()
  .build()

Keyless.authenticate(
  configuration: configuration,
  onCompletion: { /*TODO: process result*/  }
)
final configuration = BiomAuthConfig(
    shouldRemovePin: true
);

try {
    await Keyless.instance.authenticate(configuration);
    print("Pin remove successful");
} catch (error) {
    print("Pin remove failed: $error");
}
val configuration = PinAuthConfig(pin = "1234", newPin = "5678")

Keyless.authenticate(
  configuration = configuration,
  onCompletion = { /*TODO: process result*/  }
)
let configuration =  PinAuthConfig(pin: "1234", newPin: "5678")

Keyless.authenticate(
  configuration: configuration,
  onCompletion: { /*TODO: process result*/  }
)
Future<void> changePin({
  required String oldPin,
  required String newPin
}) async
val configuration = PinAuthConfig(pin = "1234", shouldRemovePin = true)

Keyless.authenticate(
  configuration = configuration,
  onCompletion = { /*TODO: process result*/  }
)
let configuration =  PinAuthConfig(pin: "1234", shouldRemovePin: true)

Keyless.authenticate(
  configuration: configuration,
  onCompletion: { /*TODO: process result*/  }
)
Future<void> removePin({
  required String pin
}) async
operation info
jwt signing
PIN utilities
val configuration = PinEnrollConfig(pin = "1234")

Keyless.enroll(
  configuration = configuration,
  onCompletion = { /*TODO: process result*/  }
)
let configuration =  PinEnrollConfig(pin: "1234")

Keyless.enroll(
  configuration: configuration,
  onCompletion: { /*TODO: process result*/  }
)
val configuration = EnrollmentConfiguration.builder
  .withPin("1234")
  .build()

Keyless.enroll(
  configuration = configuration,
  onCompletion = { /*TODO: process result*/  }
)
let configuration = Keyless.EnrollmentConfiguration.builder
  .withPin("1234")
  .build()

Keyless.enroll(
  configuration: configuration,
  onCompletion: { /*TODO: process result*/  }
)

UI Customization

The Keyless SDK can be customised in order to provide a more familiar UX when integrated in any custom app.

Theme

The SDK theme can be customized to be dark, light or system.

Keyless will use the system defined theme by default.

Keyless.UI.Color.sdkTheme = SdkTheme.SYSTEM

// default theme is SYSTEM
public enum class SdkTheme {
    DARK,
    LIGHT,
    SYSTEM
}
Keyless.UI.Color.sdkTheme = .system

// default theme is system
public enum SDKTheme {
   case dark
   case light
   case system
}

Colors

It is possible to customize the following colors:

  • primary color This color is the one that appears most frequently across the screens and components.

  • onPrimary color The color used for elements that appear on top of the PrimaryColor, ensuring clear contrast and visibility.

  • accent color Color used in some small details of the UI (e.g. Camera Frame borders)

Example

Keyless.UI.Color.primary = 0xFF1833B8
Keyless.UI.Color.onPrimary = 0xFFFED900
Keyless.UI.Color.accent = 0xFFFED900
Keyless.UI.Color.primary = .magenta
Keyless.UI.Color.onPrimary = .cyan
Keyless.UI.Color.accent = .yellow

Text

It is possible to customize the following text:

Introduction

  • title Should be no longer than two lines

  • description Should be no longer than four lines

  • cta Should be no more than two words

Enrollment

We suggest to not use more than three lines of text.

  • Instruction

    • title

    • subtitle

    • tip1 This typically suggests to stay in a well lit area

    • tip2 This typically suggests to remove any glasses or eyewear

    • tip3 This typically suggests to be alone in the frame

    • continueCta

  • Error

    • title

    • subtitle

    • cta

  • Process

    • title

    • subtitle

  • Success

    • title Showed when the enrollment has been successfully completed.

    • subtitle Showed when the enrollment has been successfully completed

    • cta Button text to show on Success screen

Authentication

We suggest to not use more than two lines of text.

  • centerFace Shown before the user positions the face.

  • scan Shown while scanning the user's face.

  • authenticate Shown while authenticating the user.

  • success Shown when the user has been authenticated successfully.

Dynamic Linking

We suggest to not use more than two lines of text.

  • Authentication.PayloadConfirmation

    • title

    • subtitle

    • denyCta

    • approveCta

Example

// Custom text for introduction
Keyless.UI.Text.Introduction.title = "(custom) Keyless Secure Authentication"
Keyless.UI.Text.Introduction.description = "(custom) Hereon, you will start face scanning..."
Keyless.UI.Text.Introduction.cta = "(custom) Continue"

// Custom text for enrollment

// prelimiar information for enrollment
Keyless.UI.Text.Enrollment.Instruction.title = "(custom) Enroll your face"
Keyless.UI.Text.Enrollment.Instruction.description = "(custom) On the next screen, we will take a photo of your face to create your account."
Keyless.UI.Text.Enrollment.Instruction.tip1 = "(custom) Center your face in the frame"
Keyless.UI.Text.Enrollment.Instruction.tip2 = "(custom) Look directly at the screen"
Keyless.UI.Text.Enrollment.Instruction.tip3 = "(custom) Ensure you are in a well-lit area"
Keyless.UI.Text.Enrollment.Instruction.continueCta = "(custom) Continue"

// Enrollment loading screen
Keyless.UI.Text.Enrollment.Process.title = "(custom 1) Hold on a few seconds"
Keyless.UI.Text.Enrollment.Process.subtitle = "(custom 2) We're creating your private key"

// Error screen
Keyless.UI.Text.Enrollment.Error.title = "(custom) Something went wrong"
Keyless.UI.Text.Enrollment.Error.subtitle = "(custom) Please try again"
Keyless.UI.Text.Enrollment.Error.cta = "(custom) Continue"

// Success screen
Keyless.UI.Text.Enrollment.Success.title = "(custom) Keyless account created successfully!"
Keyless.UI.Text.Enrollment.Success.subtitle = "(custom) Keyless account created successfully!"
Keyless.UI.Text.Enrollment.Success.cta = "(custom) Keyless account created successfully!"

// Custom text for authentication
Keyless.UI.Text.Authentication.Step1.text1 = "(custom) Please look into the camera"
Keyless.UI.Text.Authentication.Step2.text1 = "(custom) Communicating with the Keyless network"
Keyless.UI.Text.Authentication.Step3.text1 = "(custom) Approved"

// Custom text for dynamic linking
Keyless.UI.Text.Authentication.PayloadConfirmation.title = "(custom) Authentication request"
Keyless.UI.Text.Authentication.PayloadConfirmation.subtitle = "(custom) Please, approve the payment to complete the process"
Keyless.UI.Text.Authentication.PayloadConfirmation.denyCta = "(custom) Deny"
Keyless.UI.Text.Authentication.PayloadConfirmation.approveCta = "(custom) Approve"
// Custom text for enrollment

// Custom text for introduction
Keyless.UI.Text.Introduction.title = "(custom) Keyless Secure Authentication"
Keyless.UI.Text.Introduction.description = "(custom) Hereon, you will start face scanning..."
Keyless.UI.Text.Introduction.cta = "(custom) Continue"

// Custom text for enrollment

// Prelimiar information for enrollment
Keyless.UI.Text.Enrollment.Instruction.title = "(custom) Enroll your face"
Keyless.UI.Text.Enrollment.Instruction.description = "(custom) On the next screen, we will take a photo of your face to create your account."
Keyless.UI.Text.Enrollment.Instruction.tip1 = "(custom) Center your face in the frame"
Keyless.UI.Text.Enrollment.Instruction.tip2 = "(custom) Look directly at the screen"
Keyless.UI.Text.Enrollment.Instruction.tip3 = "(custom) Ensure you are in a well-lit area"
Keyless.UI.Text.Enrollment.Instruction.continueCta = "(custom) Continue"

// Enrollment loading screen
Keyless.UI.Text.Enrollment.Process.title = "(custom 1) Hold on a few seconds"
Keyless.UI.Text.Enrollment.Process.subtitle = "(custom 2) We're creating your private key"

// Error screen
Keyless.UI.Text.Enrollment.Error.title = "(custom) Something went wrong"
Keyless.UI.Text.Enrollment.Error.subtitle = "(custom) Please try again"
Keyless.UI.Text.Enrollment.Error.cta = "(custom) Continue"

// Success screen
Keyless.UI.Text.Enrollment.Success.title = "(custom) Keyless account created successfully!"
Keyless.UI.Text.Enrollment.Success.subtitle = "(custom) Keyless account created successfully!"
Keyless.UI.Text.Enrollment.Success.cta = "(custom) Keyless account created successfully!"

// Custom text for authentication
Keyless.UI.Text.Authentication.Step1.text1 = "(custom) Please look into the camera"
Keyless.UI.Text.Authentication.Step2.text1 = "(custom) Communicating with the Keyless network"
Keyless.UI.Text.Authentication.Step3.text1 = "(custom) Approved"

// Custom text for dynamic linking
Keyless.UI.Text.Authentication.PayloadConfirmation.title = "(custom) Authentication request"
Keyless.UI.Text.Authentication.PayloadConfirmation.subtitle = "(custom) Please, approve the payment to complete the process"
Keyless.UI.Text.Authentication.PayloadConfirmation.denyCta = "(custom) Deny"
Keyless.UI.Text.Authentication.PayloadConfirmation.approveCta = "(custom) Approve"

Optional Screens

It is possible to show or hide some of the Enrollment and Authentication steps above by setting to false the following fields.

The default value for all of them is true which means that the step is shown.

Enrollment

To toggle certain screens in Enrollment flow, use the following fields of a BiomEnrollConfig:

  • showInstructionsScreen: affects the Instruction screen (by default true)

  • showSuccessFeedback: affects the Success screen (by default true)

  • showFailureFeedback: affects the Error screen (by default true)

Example

// To hide all optional screens
val biomEnrollConfig = BiomEnrollConfig(
    showInstructionsScreen = false,
    showSuccessFeedback = false
    showFailureFeedback = false,
)

Keyless.enroll(
    configuration = biomEnrollConfig, 
    onCompletion = { /* handle completion */ }
)
// To hide all optional screens
let biomEnrollConfig = BiomEnrollConfig(
    showInstructionsScreen: false,
    showSuccessFeedback: false,
    showFailureFeedback: false
)
        
Keyless.enroll(
    configuration: biomEnrollConfig,
    onCompletion: { _ in /* handle completion */ }
)

Authentication

Use the following fields of a BiomAuthConfig:

  • showSuccessFeedback: affects the Step3

Example

val biomAuthConfig = BiomAuthConfig(
    showSuccessFeedback = false
)

Keyless.authenticate(
    configuration = biomAuthConfig,
    onCompletion = { /* handle completion */ }
)
let biomAuthConfig = BiomAuthConfig(
    showSuccessFeedback: false
)
        
Keyless.authenticate(
    configuration: biomAuthConfig,
    onCompletion: { _ in /* handle completion */ }
)

Fonts

It is possible to customize the font.

  • Android: requires an android.graphics.Typeface object.

  • iOS: requires a String, the name of the font.

The custom font must be set as soon as available, a good moment to do so is before calling Keyless.configure

Example

Keyless.UI.Font.customFont: Typeface = Typeface.SERIF
Keyless.UI.Font.customFont: String = "Serif"

Theme

The SDK theme can be customized to be dark, light or system.

Keyless will use the system defined theme by default.

Keyless.UI.Color.sdkTheme = SdkTheme.SYSTEM

// default theme is SYSTEM
public enum class SdkTheme {
    DARK,
    LIGHT,
    SYSTEM
}
Keyless.UI.Color.sdkTheme = .system

// default theme is system
public enum SDKTheme {
   case dark
   case light
   case system
}

Colors

It is possible to customize the following colors:

  • primary color This color is the one that appears most frequently across the screens and components.

  • onPrimary color The color used for elements that appear on top of the PrimaryColor, ensuring clear contrast and visibility.

Example

Keyless.UI.Color.primary = 0xFF1833B8
Keyless.UI.Color.onPrimary = 0xFFFED900
Keyless.UI.Color.primary = .magenta
Keyless.UI.Color.onPrimary = .cyan
final uiCustomization = UICustomization(
    primaryColor: 0xFF1833B8,
    onPrimaryColor: 0xFFFED900,
);

// Pass the UI customization when configuring the SDK
final setupConfiguration = SetupConfiguration(
    apiKey: "YOUR_API_KEY",
    hosts: ["YOUR_HOSTS"],
    uiCustomization: uiCustomization
);

Text

It is possible to customize the following text:

Enrollment

We suggest to not use more than three lines of text.

  • Step0 (information screen before enrollment)

    • title

    • description

    • prerequisiteCenterFace

    • prerequisiteDirectLook

    • prerequisiteWellLitArea

    • prerequisiteRemoveEyeWear

    • continueCta

    • closeScreenButtonContentDescription (the content description of the close button for accessibility)

    • prerequisiteAloneInPhoto deprecated (will no longer be shown)

  • Step1

    • text1 Showed before the user face is framed.

  • step1Success

    • message Showed after the user face is framed with a successful animation.

  • Step2

    • text1 Showed after the user face has been framed and the progress is between 0% and 33%.

    • text2 Showed after the user face has been framed and the progress is between 34% and 66%.

    • text3 Showed after the user face has been framed and the progress is between 67% and 100%.

    • subtitle1 Showed during the entire enrollment progress

  • Step3

    • text1 Showed when the enrollment has been successfully completed.

    • subtitle1 Showed when the enrollment has been successfully completed

Authentication

We suggest to not use more than two lines of text.

  • Step1

    • text1 Showed before the user face is framed.

  • Step2

    • text1 Showed after the user face has been framed.

  • Step3

    • text1

      • Showed when the authentication has been successfully completed.

Dynamic Linking

We suggest to not use more than two lines of text.

  • Authentication.PayloadConfirmation

    • title

    • subtitle

    • denyCta

    • approveCta

Example

// Custom text for enrollment

// prelimiar information for enrollment
Keyless.UI.Text.Enrollment.Step0.title = "(custom) Enroll your face"
Keyless.UI.Text.Enrollment.Step0.description = "(custom) On the next screen, we will take a photo of your face to create your account."

Keyless.UI.Text.Enrollment.Step0.prerequisiteCenterFace = "(custom) Center your face in the frame"
Keyless.UI.Text.Enrollment.Step0.prerequisiteDirectLook = "(custom) Look directly at the screen"
Keyless.UI.Text.Enrollment.Step0.prerequisiteWellLitArea = "(custom) Ensure you are in a well-lit area"
Keyless.UI.Text.Enrollment.Step0.prerequisiteRemoveEyeWear = "(custom) Remove any eyewear or hats"
Keyless.UI.Text.Enrollment.Step0.continueCta = "(custom) Continue"

// remaining steps of the enrollment
Keyless.UI.Text.Enrollment.Step1.text1 = "(custom) Put your face within the frame"
Keyless.UI.Text.Enrollment.Step1Success.message = "(custom) Photo captured successfully"

Keyless.UI.Text.Enrollment.Step2.text1 = "(custom 1) Enrolling, please wait…"
Keyless.UI.Text.Enrollment.Step2.text2 = "(custom 2) Enrolling, please wait…"
Keyless.UI.Text.Enrollment.Step2.text3 = "(custom 3) Enrolling, please wait…"
Keyless.UI.Text.Enrollment.Step3.text1 = "(custom) Keyless account created successfully!"

// Custom text for authentication
Keyless.UI.Text.Authentication.Step1.text1 = "(custom) Please look into the camera"
Keyless.UI.Text.Authentication.Step2.text1 = "(custom) Communicating with the Keyless network"
Keyless.UI.Text.Authentication.Step3.text1 = "(custom) Approved"

// Custom text for dynamic linking
Keyless.UI.Text.Authentication.PayloadConfirmation.title = "(custom) Authentication request"
Keyless.UI.Text.Authentication.PayloadConfirmation.subtitle = "(custom) Please, approve the payment to complete the process"
Keyless.UI.Text.Authentication.PayloadConfirmation.denyCta = "(custom) Deny"
Keyless.UI.Text.Authentication.PayloadConfirmation.approveCta = "(custom) Approve"
// Custom text for enrollment

// prelimiar information for enrollment
Keyless.UI.Text.Enrollment.Step0.title = "(custom) Enroll your face"
Keyless.UI.Text.Enrollment.Step0.description = "(custom) On the next screen, we will take a photo of your face to create your account."

Keyless.UI.Text.Enrollment.Step0.prerequisiteCenterFace = "(custom) Center your face in the frame"
Keyless.UI.Text.Enrollment.Step0.prerequisiteDirectLook = "(custom) Look directly at the screen"
Keyless.UI.Text.Enrollment.Step0.prerequisiteWellLitArea = "(custom) Ensure you are in a well-lit area"
Keyless.UI.Text.Enrollment.Step0.prerequisiteRemoveEyeWear = "(custom) Remove any eyewear or hats"
Keyless.UI.Text.Enrollment.Step0.continueCta = "(custom) Continue"

// remaining steps of the enrollment
Keyless.UI.Text.Enrollment.Step1.text1 = "(custom) Put your face within the frame"
Keyless.UI.Text.Enrollment.Step1Success.message = "(custom) Position your face within the frame"

Keyless.UI.Text.Enrollment.Step2.text1 = "(custom 1) Enrolling, please wait…"
Keyless.UI.Text.Enrollment.Step2.text2 = "(custom 2) Enrolling, please wait…"
Keyless.UI.Text.Enrollment.Step2.text3 = "(custom 3) Enrolling, please wait…"
Keyless.UI.Text.Enrollment.Step3.text1 = "(custom) Keyless account created successfully!"

// Custom text for authentication
Keyless.UI.Text.Authentication.Step1.text1 = "(custom) Please look into the camera"
Keyless.UI.Text.Authentication.Step2.text1 = "(custom) Communicating with the Keyless network"
Keyless.UI.Text.Authentication.Step3.text1 = "(custom) Approved"

// Custom text for dynamic linking
Keyless.UI.Text.Authentication.PayloadConfirmation.title = "(custom) Authentication request"
Keyless.UI.Text.Authentication.PayloadConfirmation.subtitle = "(custom) Please, approve the payment to complete the process"
Keyless.UI.Text.Authentication.PayloadConfirmation.denyCta = "(custom) Deny"
Keyless.UI.Text.Authentication.PayloadConfirmation.approveCta = "(custom) Approve"
final uiCustomization = UICustomization(
    // Enrollment Step 0
    enrollmentStep0Title: "(custom) Enroll your face",
    enrollmentStep0Description: "(custom) On the next screen, we will take a photo of your face to create your account.",
    enrollmentStep0PrerequisiteCenterFace: "(custom) Center your face in the frame",
    enrollmentStep0PrerequisiteDirectLook: "(custom) Look directly at the screen",
    enrollmentStep0PrerequisiteWellLitArea: "(custom) Ensure you are in a well-lit area",
    enrollmentStep0PrerequisiteRemoveEyeWear: "(custom) Remove any eyewear or hats",
    enrollmentStep0ContinueCta: "(custom) Continue",
    
    // Enrollment Steps
    enrollmentStep1Text: "(custom) Put your face within the frame",
    enrollmentStep1SuccessText: "(custom) Photo captured successfully",
    
    enrollmentStep2Text1: "(custom 1) Enrolling, please wait…",
    enrollmentStep2Text2: "(custom 2) Enrolling, please wait…",
    enrollmentStep2Text3: "(custom 3) Enrolling, please wait…",
    enrollmentStep2Subtitle: "(custom) Processing your enrollment",
    
    enrollmentStep3Text: "(custom) Keyless account created successfully!",
    enrollmentStep3Subtitle: "(custom) You're all set!",
    
    // Authentication Steps
    authenticationStep1: "(custom) Please look into the camera",
    authenticationStep2: "(custom) Communicating with the Keyless network",
    authenticationStep3: "(custom) Approved",
);

// Pass the UI customization when configuring the SDK
final setupConfiguration = SetupConfiguration(
    apiKey: "YOUR_API_KEY",
    hosts: ["YOUR_HOSTS"],
    uiCustomization: uiCustomization
);

Optional Screens

It is possible to show or hide some of the Enrollment and Authentication steps above by setting to false the following fields.

The default value for all of them is true which means that the step is shown.

Enrollment

Use the following fields of a BiomEnrollConfig:

  • showScreenInstructions: affects the Step0

  • showScreenSuccessFaceCapture: affects the Step1Success

  • showScreenSuccessFlow: affects the Step3

Example

val biomEnrollConfig = BiomEnrollConfig(
    showScreenInstructions = false,
    showScreenSuccessFaceCapture = false,
    showScreenSuccessFlow = false
)

Keyless.enroll(
    configuration = biomEnrollConfig, 
    onCompletion = { /* handle completion */ }
)
let biomEnrollConfig = BiomEnrollConfig(
    showScreenSuccessFlow: false,
    showScreenSuccessFaceCapture: false,
    showScreenInstructions: false
)
        
Keyless.enroll(
    configuration: biomEnrollConfig,
    onCompletion: { _ in /* handle completion */ }
)

Authentication

Use the following fields of a BiomAuthConfig:

  • showScreenSuccessFlow: affects the Step3

Example

val biomAuthConfig = BiomAuthConfig(
    showScreenSuccessFlow = false
)

Keyless.authenticate(
    configuration = biomAuthConfig,
    onCompletion = { /* handle completion */ }
)
let biomAuthConfig = BiomAuthConfig(
    showScreenSuccessFlow: false
)
        
Keyless.authenticate(
    configuration: biomAuthConfig,
    onCompletion: { _ in /* handle completion */ }
)

Fonts

It is possible to customize the font.

  • Android: requires an android.graphics.Typeface object.

  • iOS: requires a String, the name of the font.

The custom font must be set as soon as available, a good moment to do so is before calling Keyless.configure

Example

Keyless.UI.Font.customFont: Typeface = Typeface.SERIF
Keyless.UI.Font.customFont: String = "Serif"
final uiCustomization = UICustomization(
    customFont: "Serif"
);

1️⃣ Getting started

In this short guide, you will learn how to integrate the Keyless SDK in your Android or iOS mobile application, and enroll and authenticate users through the Keyless platform.

Before jumping into your code editor, make sure that you're familiar with the various components of the authentication system, and common biometic authentication flows.

Prerequisites

Make sure you have both required API keys, and the list of productions hosts from your Keyless contact:

  • YOUR_CLOUDSMITH_TOKEN to download the SDK from cloudsmith repository

  • KEYLESS_API_KEY to configure the mobile SDK

  • KEYLESS_HOSTS a list of node URLs. Urls in KEYLESS_HOSTS must NOT contain any trailing slashes /.

Review the Keyless SDK requirements:

The Keyless SDK uses

  • Android 6.0 (API level 23) and above

  • Kotlin 1.9.25

  • Gradle 8.7

  • Android Gradle Plugin 8.3.0

  • AndroidX

The Keyless SDK uses

  • iOS 13

  • Swift 5.1

  • Cocoapods 1.15.2

Set up a physical iOS device for running your app and enable the following permissions:

  • Enable Camera permissions: add the Privacy - Camera Usage Description key in your project’s Info.plist by adding the following (in XCode under Project > Info):

    <key>NSCameraUsageDescription</key>
    <string>Keyless needs access to your camera to enroll and authenticate you. Keyless cannot be used without your camera. Please allow camera permissions.</string>
  • Enable background processing: necessary to synchronize Keyless data. You can find a comprehensive guide in the apple documentation. In a nutshell here is what you need to do: enable the Background processing mode under Signing & Capabilities/Background Modes. Then, add the following in your project’s Info.plit (in XCode, under the key Permitted background task scheduler identifiers):

The Keyless SDK uses

  • Flutter 3.0 or higher

  • Dart 3.0 or higher

  • All requirements from both Android and iOS platforms

Set up the required permissions as described in the Android and iOS tabs.

Installation

  1. To allow Keyless to handle the result of registerForActivityResult, you must call Keyless from an Activity implementing ActivityResultCaller.

    Extend any androidX activity that implements the interface for you, for example let your Activity extend the generic ComponentActivity or the more widespread AppCompatActivity.

  2. If you use Proguard, add the following rules to your Proguard configuration file:

    # Keyless Proguard
    -keep class io.keyless.sdk.** {*;}
    -keepclassmembers class io.keyless.sdk.** {*;}
  3. In the the repositories section of the settings.gradle file of your Android application, add the following snippet, replacing YOUR_CLOUDSMITH_TOKEN with the CloudSmith token provided to you by Keyless.

    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            google()
            mavenCentral()
            maven {
                url "https://dl.cloudsmith.io/YOUR_CLOUDSMITH_TOKEN/keyless/partners/maven/"
            }
        }
    }
  4. In the dependencies block of your project build.gradle file, typically app/build.gradle, add:

    dependencies {
    // ...
    
    implementation 'io.keyless:keyless-android-sdk:+'
    }
  5. In the android block of your project build.gradle file, typically app/build.gradle, make sure you have the following options:

    android {
        // ...
    
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    
        // add the following only if you're using Kotlin
        kotlinOptions {
            jvmTarget = "1.8"
        }
    }
  1. Create a Podfile if you don’t already have one

    $ cd your-project-directory
    $ pod init
  2. Add the KeylessSDK pod to your Podfile

    # This is the Keyless repository for partners
    source 'https://dl.cloudsmith.io/YOUR_CLOUDSMITH_TOKEN/keyless/partners/cocoapods/index.git'
    
    target 'MyApp' do
        use_frameworks!
    
        # Add the Keyless pod
        pod 'KeylessSDK'
    end
  3. Add the following at the bottom of your Podfile

    post_install do |installer|
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
        config.build_settings['ENABLE_BITCODE'] = 'NO'
        end
    end
    end
  4. Install the pods.

    $ pod install
    
    Analyzing dependencies
    Cloning spec repo `cloudsmith-YOUR_CLOUDSMITH_TOKEN-keyless-partners-cocoapods-index` from `https://dl.cloudsmith.io/YOUR_CLOUDSMITH_TOKEN/keyless/partners/cocoapods/index.git`
  1. Add the Keyless SDK repository

    echo '${CLOUDSMITH_TOKEN}' | dart pub token add https://dart.cloudsmith.io/keyless/flutter/
  2. Add the Keyless SDK dependency to your pubspec.yaml:

    dart pub add keyless_flutter_sdk:PACKAGE_VERSION --hosted-url https://dart.cloudsmith.io/keyless/flutter/
  3. Follow the installation steps in both the Android and iOS tabs to set up the native SDKs in their respective platforms.

  4. Run flutter pub get to download the dependencies:

    flutter pub get

You also need to bridge the native SDKs that are used by the flutter SDK. You can do that in the android and ios sections of your flutter project.

Android

Add the following line in the root build.gradle:

allprojects {
    repositories {
        google()
        mavenCentral()
        maven {
            setUrl("https://dl.cloudsmith.io/YOUR_CUSTOM_TOKEN/keyless/partners/maven/")
        }
    }
}  

Where YOUR_CUSTOM_TOKEN should be replaced with the cloudsmith token provided by the Keyless integration support team.

In the example repo you can see that we are using a file to simulate env properties containing the cloudsmith token. You will read the following snippet:

allprojects {
    repositories {
        google()
        mavenCentral()
        maven {
            setUrl("https://dl.cloudsmith.io/$cloudsmithToken/keyless/partners/maven/")
        }
    }
}

You can read the cloudsmith token from a file cloudsmith.properties that you need to create at the root project directory. File content shall be the following: cloudsmithToken=YOUR_CUSTOM_TOKEN

Then, open your Android.manifest file and add the following lines inside the <application> tag:

        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge">
 
            <meta-data
                android:name="io.keyless.fluttersdk.KeylessInitializer"
                android:value="androidx.startup" />
        </provider>

iOS

Make sure to target at least iOS 13 in your project.

Open your PodFile and add the following line:

source 'https://dl.cloudsmith.io/YOUR_CUSTOM_TOKEN/keyless/partners/cocoapods/index.git'

Where YOUR_CUSTOM_TOKEN should be replaced with the cloudsmith token provided by the Keyless integration support team.

In the example repo you can see that we are using a file to simulate env properties containing the cloudsmith token. You will read the following snippet:

source 'https://dl.cloudsmith.io/'+ ENV['cloudsmithToken'] +'/keyless/partners/cocoapods/index.git'

File content shall be the following: cloudsmithToken=YOUR_CUSTOM_TOKEN

Then, navigate to your Info.plist file, typically located in a folder called with your project name (e.g. Runner).

Add the following permissions declaration to this file (if not present already):

<key>NSCameraUsageDescription</key> 
<string>Keyless needs access to your camera to enroll and authenticate you. Keyless cannot be used without your camera. Please allow camera permissions.</string> 
<key>NSMicrophoneUsageDescription</key> 
<string>Keyless needs access to your camera to enroll and authenticate you. Keyless cannot be used without your camera. Please allow camera permissions.</string>   

Essential configuration

There is some essential configuration to do before you can use the Keyless SDK.

  1. Initialize the Keyless SDK in your Application class:

    // MainApplication
    override fun onCreate() {
        super.onCreate()
        // Initialize Keyless
        Keyless.initialize(this)
    }
  2. Add your application to the Manifest

    <application
        ...
        android:name=".MainApplication"
        ...
    </application>
  3. Configure the Keyless SDK from your MainActivity, ViewModel or any class you use to communicate with Keyless. Note that configure is asynchronous so wait for the completion callback before calling the next Keyless APIs

    The configure method requires a SetupConfig as parameter, as well as the KEYLESS_API_KEY and KEYLESS_HOSTS you received from Keyless. You should listen to the result as follows:

    val setupConfig = SetupConfig(
            apiKey = "KEYLESS_API_KEY",
            hosts = listOf("KEYLESS_HOSTS")
        )
    
     Keyless.configure(setupConfig) { result ->
        when (result) {
            is Keyless.KeylessResult.Success -> {
                Log.d("KeylessSDK", "configure success")
                // Keyless is ready, you can now call
                // enroll/authenticate/deEnroll ...
            }
            is Keyless.KeylessResult.Failure ->{
                Log.d("KeylessSDK", "configure error")
                // Inspect result.error for more info
             }
         }
     }
  1. Create an instance object of Keyless.SetupConfiguration and pass it to the Keyless.configure method, typically this is done in your app’s application(:didFinishLaunchingWithOptions: method:).

    let setupConfig = SetupConfig(
            apiKey: "KEYLESS_API_KEY",
            hosts: ["KEYLESS_HOSTS"]
        )
    
    if let error = Keyless.configure(configuration: setupConfig) {
        print("Keyless.Configure failed with error: \(error)")
    }
  1. Initialize the Keyless SDK in your Application class:

    // MainApplication
    override fun onCreate() {
        super.onCreate()
        // Initialize Keyless
        Keyless.initialize(this)
    }
  2. Add your application to the Manifest

    <application
        ...
        android:name=".MainApplication"
        ...
    </application>
  3. Configure the Keyless SDK from your MainActivity, ViewModel or any class you use to communicate with Keyless. Note that configure is asynchronous so wait for the completion callback before calling the next Keyless APIs

    The configure method requires a SetupConfiguration as parameter, as well as the KEYLESS_API_KEY and KEYLESS_HOSTS you received from Keyless. You should listen to the result as follows:

    val setupConfiguration = SetupConfiguration.builder
        .withApiKey("KEYLESS_API_KEY")
        .withHosts(listOf("KEYLESS_HOSTS"))
        .build()
    
     Keyless.configure(setupConfiguration) { result ->
        when (result) {
            is Keyless.KeylessResult.Success -> {
                Log.d("KeylessSDK", "configure success")
                // Keyless is ready, you can now call
                // enroll/authenticate/deEnroll ...
            }
            is Keyless.KeylessResult.Failure ->{
                Log.d("KeylessSDK", "configure error")
                // Inspect result.error for more info
             }
         }
     }
  1. Create an instance object of Keyless.SetupConfiguration and pass it to the Keyless.configure method, typically this is done in your app’s application(:didFinishLaunchingWithOptions: method:).

    let setupConfiguration = Keyless.SetupConfiguration.builder
        .withApiKey("KEYLESS_API_KEY")
        .withHosts(["KEYLESS_HOSTS"])
        .build()
    
    if let error = Keyless.configure(configuration: setupConfiguration) {
        print("Keyless.Configure failed with error: \(error)")
    }
  1. Import the required packages:

    import 'package:keyless_flutter_sdk/keyless.dart';
    import 'package:keyless_flutter_sdk/models/configurations/setup_configuration.dart';
  2. Configure the Keyless SDK in your app's initialization code:

    final setupConfiguration = SetupConfiguration(
        apiKey: "KEYLESS_API_KEY",
        hosts: ["KEYLESS_HOSTS"],
    );
    
    try {
        await Keyless.instance.configure(setupConfiguration);
        print("Keyless SDK configured successfully");
        // Keyless is ready, you can now call
        // enroll/authenticate/deEnroll ...
    } catch (error) {
        print("Failed to configure Keyless SDK: $error");
        // Handle error
    }
  3. Make sure to set up the required permissions in both Android and iOS platforms as described in their respective tabs.

Additional configuration

As well as the essential configuration parameters, you can also specify the optional configuration parameters detailed in the following sections. You can inspect the Config class to see optional parameters (or the builder to see exposed methods)

Logging

Logging is disabled by default.

Please note logs do not include any Personally Identifiable Information (PII).

Keyless allows you to:

  1. enable logging to the Keyless infrastructure - keylessLogsConfiguration

  2. collect logging for you own analytics without sending them to the Keyless infrastructure - customLogsConfiguration

val setup = SetupConfig(
    apiKey = "...",
    hosts = listOf(""),
    keylessLogsConfiguration = LogsConfiguration(
        enabled = true,
        logLevel = LogLevels.INFO
    ),
    customLogsConfiguration = LogsConfiguration(
        enabled = true
    )
)
let configuration = SetupConfig(
    apiKey: "some api key",
    hosts: ["some.host"],
    keylessLogsConfiguration: KeylessLogsConfiguration(enabled: true),
    customLogsConfiguration: CustomLogsConfiguration(enabled: true, logLevel: .INFO, callback: { event in
        print(event)
    })
)

Keyless.configure(setupConfiguration: configuration) { error in
    // handle error
}
val setupConfiguration = SetupConfiguration.builder
    .withApiKey("")
    .withHosts(listOf("..."))
    .withLogging(
        keylessLogsConfiguration = LogsConfiguration(enabled = true),
        customLogsConfiguration = LogsConfiguration(enabled = true, logLevel = LogLevels.INFO)
    )
    .build()
let configuration = Keyless.SetupConfiguration
  .builder
  .withApiKey("some api key")
  .withHosts(["some.host"])
  .withLogging(
    keylessLogsConfiguration: KeylessLogsConfiguration(enabled: true, logLevel: .INFO),
    customLogsConfiguration: CustomLogsConfiguration(enabled: true, callback: { event in
      print(event)
    })
  )
  .build()
 
Keyless.configure(setupConfiguration: configuration) { error in
  // handle error
}
final configuration = SetupConfiguration(
              apiKey: apiKey,
              hosts: [host],
              loggingEnabled: true,
              loggingLevel: LogLevel.info));

try {
  final result = await Keyless.instance.configure(configuration);
  print("Configure finished succcessfully.");
} catch (error) {
  print("Configure finished with error: $error");
}

Collect logs after setting up custom logging with customLogsConfiguration option:

  1. Start collecting the Keyless.customLogs flow (This must happen before a Keyless.configure() to get all Logs Events):

Keyless.customLogs.collect { logEvent ->
    // handle the logEvent
}
  1. Configure the SDK with the following SetupConfig:

val setup = SetupConfig(
    apiKey = "...",
    hosts = listOf(""),
    customLogsConfiguration = LogsConfiguration(
        enabled = true,
        logLevel = LogLevels.INFO // This is optional and defaults to INFO
    )
)
var myEventCollection = [LogEvent]()
    let configuration = Keyless.SetupConfiguration
      .builder
      .withApiKey("some api key")
      .withHosts(["some.host"])
      .withLogging(
        keylessLogsConfiguration: KeylessLogsConfiguration(enabled: true, logLevel: .INFO),
        customLogsConfiguration: CustomLogsConfiguration(enabled: true, callback: { event in
          myEventCollection.append(event)
        })
      )
     .build()
    Keyless.configure(setupConfiguration: configuration) { error in
      // handle error
    }
}

Logging Levels

You may set different logging levels to provide more or less information in logs.

The available levels are the following:

  • INFO (default)

  • DEBUG

  • TRACE

The TRACE level provides the following additional data:

  • userId

  • devicePublicSigningKey

  • coreLogHistory (used for detailed debugging)

Liveness Lockout policy

Since version 5.0.0 of the SDK the lockout policy is no longer processed on the client for security reasons. The lockout policy is applied only during authentication and can be confiured server side.

Keyless has both client side (applicable to a specific device) and server side (applicable to all users and devices) lockout policies to help prevent brute force attacks.

Client side lockout is configurable in the SDK, and determines how many failed login attempts (lockoutAttemptsThreshold) are allowed over a set time period (lockoutAttemptsResetAfter) before the user is locked out for the set duration (lockoutDuration) on that device.

Account lockoutDuration must be greater than or equal to the lockoutAttemptsResetAfter so that it is not reset by lockoutAttemptsResetAfter.

lockoutDuration: Long,                //seconds - default 300 seconds
lockoutAttemptsResetAfter: Long,      //seconds - default 180 seconds
lockoutAttemptsThreshold: Int         //number  - default 5 attempts

Server side lockout works similarly, except applies to all authentication devices for a specific user, and is configured to lock a user out for 10 minutes after 5 failed attempts. A successful login resets the count of failed authentication attempts to zero.

Tampered device check

Keyless provides a tampered device check that can be enabled within the Mobile SDK configurations. This service looks for signs of rooting, or jailbreaking, whereby a smartphone or tablet may have been tampered with in order to gain greater administrative control over it, a tactic used by malicious actors. You can enable the check by:

  • Calling the withTamperedDeviceCheck() on the SetupConfigurationBuilder if using the legacy SetupConfiguration.

  • Setting withDevicePrivileges to true if using the new SetupConfig.

If you enable this check, and the SDK detects that the phone was tampered with, the Keyless SDK will return an error and prevent the user from authenticating. As ever, we advise clients that no security measure is perfect and we can't guarantee 100% detection success rate. Therefore we recommend taking advice or carry out further research on how else to harden the security of your own app if this is a particular concern.

Enrollment circuits

To speed up enrollment you can use the numberOfEnrollmentCircuits to upload less than the default of five circuits upon enrollment. The remainder (50 - numberOfEnrollmentCircuits) are uploaded asynchronously.

Shared circuits

This is the target desired number of circuits kept on the server. To set this number use the numberOfSharedCircuits.

Networking module

If you need to perform network requests on behalf of Keyless SDK, you can implement the KLNetworkingModule interface. Set the networkingModule parameter with an instance of the KLNetworkingModule:

public interface KLNetworkingModule {
    public fun sendHTTPRequest(
        baseUrl: String,
        path: String,
        method: String,
        headers: Map<String, String>,
        body: String
    ): KLHTTPResponse

    public data class KLHTTPResponse(
        val errorCode: Int,
        val httpCode: Int,
        val responseBody: String
    )
}
public protocol KLNetworkingModule {
    func sendHTTPRequest (
        host: String,
        url : String,
        method: String,
        headers : [String: String],
        body : String) -> KLHTTPResponse
}

public struct KLHTTPResponse {
    var errorCode: Int
    var httpCode: Int
    var responseBody : String
    public init(errorCode: Int, httpCode: Int, responseBody: String) {
        self.errorCode = errorCode
        self.httpCode = httpCode
        self.responseBody = responseBody
    }
}
<key>NSCameraUsageDescription</key>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>YOUR APP IDENTIFIER</string>
</array>