Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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.
During enrollment, your mobile app invokes the enroll
method from the Keyless SDK, and then:
Guides the user through capturing a biometric signal with the device camera.
Interacts with Keyless to generate a new user identifier (Keyless ID), which is then returned to your mobile app.
Authentication involves your application server, your mobile app, and the Keyless network, as depicted in Figure 2:
This process starts when the user performs an action that requires authentication using your mobile app.
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.
The Keyless SDK authenticates the user by capturing the user’s biometrics using the mobile device’s camera.
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.
The app sends the token to your application server, which verifies it.
If the authentication token is valid, the application server completes the transaction and notifies your mobile app.
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.
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
Keyless.showIntroductionScreen {
// Perform actions after user taps CTA
}
Keyless.showIntroductionScreen {
// Perform actions after user taps CTA
}
This set of API calls allows you to fetch and manipulate Keyless enabled devices.
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.
Use cameraDelaySeconds
to specify the delay (in seconds) between when the camera preview appears, and when the liveness processing starts.
Use showSuccessFeedback
to show a Success
text on top of the screen when the DeEnroll is successful.
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.
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.
Retrieve the user identifier with Keyless.getUserId()
:
The device is identified by its public signing key. To retrieve the public signing key use Keyless.getDevicePublicSigningKey()
:
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:
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.
Learn how the Keyless components interact with your app.
Learn how the Keyless SDK components can be integrated into a mobile application and backend server, to enable biometric authentication.
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.
The most common authentications scenarios for the Keyless SDK are:
access to a web application
access on a mobile 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.
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.
As mentioned, Keyless is composed of two main blocks:
Keyless SDK
Keyless backend / Confirmation API Service
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
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.
Keyelss mobile SDK can generate a signed a JWT containing a custom payload. You can use the signed JWT to implement .
Pass JwtSigningInfo
to the authentication to generate a signed JWT:
The AuthenticationSuccess contains the following fields:
signedJwt
: the signed JWT.
This page explains what we mean by account recovery and the context you may need to integrate it.
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 ).
Keyless is able to recover an account from what we refer to as client or temporary state.
The client or temporary state is obtained:
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.
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.
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
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");
}
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");
}
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
.
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.
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
);
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.
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
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.
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");
}
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*/ }
)
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 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 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.
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
.
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.
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
}
}
}
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");
}
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;
}
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;
}
Use cameraDelaySeconds
to specify the delay (in seconds) between when the camera preview appears, and when the liveness processing starts.
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.
You can specify a payload to be added to a JWT signed by Keyless with the jwtSigningInfo
parameter, more in JWT signing.
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.
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.
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.
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
.
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.
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
// 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
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.
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.
Depending on the builder methods you enable, Keyless will populate the corresponding fields in the AuthenticationSuccess
result reported below.
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 .
Use cameraDelaySeconds
to specify the delay (in seconds) between when the camera preview appears, and when the liveness processing starts.
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.
You can specify a payload to be added to a JWT signed by Keyless with the jwtSigningInfo
parameter, more in .
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.
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 .
Use the shouldRetrieveTemporaryState
parameter to creata a temporary state useful for the .
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
.
You can configure the enrollment process with optional parameters in your PhotoEnrollConfig()
instance.
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.
Get user devices
The user id
Delete user device
The user id
Public signing key
Get a user's pending operations
The user id
Get a customer operation
Unique operation identifier set and managed by the client
Create a pending operation
The user id
User id set and managed by the client
Unique operation identifier set and managed by the client
Operation payload set and managed by the client
Get the customer public key in PEM format
Verify a signed JWT message. The only format accepted for now is a JWT generated by a PIN signature
The signed JWT message
Delete an user and all associated entities
The user id
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
}
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
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;
}
// 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)
}
})
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 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 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");
}
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:
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.
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.
The following sections contain some examples on implementing SCA with Keyless.
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.
Once the user approves the transaction data, Keyless starts the authentication to:
Authenticate the payer with the device factor and the biometric factor using Keyless MFA.
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".
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.
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.
This API allows for backend-to-backend communication between your backend and the Keyless servers.
[
{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"
}
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.
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.
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.
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.
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.
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.
Highlights
Fix: fix client state retro compatibility with version >2.0.0 of the Keyless Agent
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.
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.
Feature: improved PSD2 compliance - docs
Feature: account recovery - docs
Fix: remove lottie dependency
UX: remove success animation in favor of static image
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
Bugfix: internal data cleanup
Performance improvements: avoid unnecessary assets extraction
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
Feature: enroll with Keyless from an Auth0 user’s IdToken
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
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.
To enroll using the PIN factor create the following configuration:
To enroll multiple authentication factors you need call Keyless.enroll
for each factor.
To authenticate using the PIN factor create the following configuration:
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:
To remove the PIN factor, while still keeping the biometric factor, perform a biometric authentication using the following configuration:
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
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*/ }
)
The Keyless SDK can be customised in order to provide a more familiar UX when integrated in any custom app.
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
}
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)
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
It is possible to customize the following text:
title Should be no longer than two lines
description Should be no longer than four lines
cta Should be no more than two words
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
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.
We suggest to not use more than two lines of text.
Authentication.PayloadConfirmation
title
subtitle
denyCta
approveCta
// 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"
It is possible to show or hide some of the Enrollment and Authentication steps above by setting to false
the following fields.
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 */ }
)
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 */ }
)
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
Keyless.UI.Font.customFont: Typeface = Typeface.SERIF
Keyless.UI.Font.customFont: String = "Serif"
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
}
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.
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
);
It is possible to customize the following text:
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
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.
We suggest to not use more than two lines of text.
Authentication.PayloadConfirmation
title
subtitle
denyCta
approveCta
// 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
);
It is possible to show or hide some of the Enrollment and Authentication steps above by setting to false
the following fields.
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 */ }
)
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 */ }
)
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
Keyless.UI.Font.customFont: Typeface = Typeface.SERIF
Keyless.UI.Font.customFont: String = "Serif"
final uiCustomization = UICustomization(
customFont: "Serif"
);
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.
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
The Keyless SDK uses
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.
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.
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.** {*;}
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/"
}
}
}
In the dependencies
block of your project build.gradle
file, typically app/build.gradle
, add:
dependencies {
// ...
implementation 'io.keyless:keyless-android-sdk:+'
}
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"
}
}
Create a Podfile if you don’t already have one
$ cd your-project-directory
$ pod init
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
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
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`
Add the Keyless SDK repository
echo '${CLOUDSMITH_TOKEN}' | dart pub token add https://dart.cloudsmith.io/keyless/flutter/
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/
Follow the installation steps in both the Android and iOS tabs to set up the native SDKs in their respective platforms.
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.
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>
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>
There is some essential configuration to do before you can use the Keyless SDK.
Initialize the Keyless SDK in your Application
class:
// MainApplication
override fun onCreate() {
super.onCreate()
// Initialize Keyless
Keyless.initialize(this)
}
Add your application to the Manifest
<application
...
android:name=".MainApplication"
...
</application>
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
}
}
}
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)")
}
Initialize the Keyless SDK in your Application
class:
// MainApplication
override fun onCreate() {
super.onCreate()
// Initialize Keyless
Keyless.initialize(this)
}
Add your application to the Manifest
<application
...
android:name=".MainApplication"
...
</application>
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
}
}
}
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)")
}
Import the required packages:
import 'package:keyless_flutter_sdk/keyless.dart';
import 'package:keyless_flutter_sdk/models/configurations/setup_configuration.dart';
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
}
Make sure to set up the required permissions in both Android and iOS platforms as described in their respective tabs.
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 is disabled by default.
Please note logs do not include any Personally Identifiable Information (PII).
Keyless allows you to:
enable logging to the Keyless infrastructure - keylessLogsConfiguration
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:
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
}
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
}
}
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)
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.
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.
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.
This is the target desired number of circuits kept on the server. To set this number use the numberOfSharedCircuits
.
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>