Mobile SDK: Android

This page will show you how to install and use the FuturaeKit SDK for Android.

Prerequisites

The Android Futurae SDK v3 requires minimum Android version 6, API level (minSdkVersion) 23. Before reading the rest of this guide, please make sure that you first read the Mobile SDK: Introduction page.

Release Information

You can find information about the Android FuturaeKit SDK release history and changes here for stable releases and here for the latest beta releases.

What’s new

SDK v3 has the following changes:

  1. The Public API was reworked. Method signatures changes to achieve maximum convenience for the SDK clients. The rework includes modern DSL and semantical grouping of the SDK operations.
  2. v3 introduces a new threading-framework-agnostic way to deal with Asynchronous operations.
  3. Global SDK state tracking. SDK state
  4. Reworked error model. Refer to Error Handling
  5. The internal SDK systems were reworked to achieve maximum stability and testability. Legacy pieces of java code were rewritten in Kotlin to benefit from most modern language features.

Migrate from SDK v2.x.x

Assuming that your app already integrates SDK v2, the steps to migrate to the latest version v3, which features the in-depth internal rework, coroutines support and new public API, are described below:

Migration guide

What needs to be done:

  1. Update method invocations where appropriate. SDK v3 has all functionality that v2 has, but method names and signatures changed. The overview of the provided methods can be found here.
  2. Update imports for the entities that belong to futurae’s SDK public API. Entities designed to be accessible by the host app are located under the public_api package.
  3. Review and accommodate error handling if appropriate. Refer to Error Handling for more info.

Installation

We are going to assume that you are using Android Studio for your development. You can install FuturaeKit into your project using Maven.

Maven

The way to get the FuturaeKit SDK for Android is through Github Packages maven repository.

To do so add the following lines to your build.gradle file:

repositories {
  maven {
    url "https://maven.pkg.github.com/Futurae-Technologies/android-sdk" // or /android-sdk-beta for beta versions 
    credentials {
      username = "$GITHUB_ACTOR"
      password = "$GITHUB_TOKEN"
    }
  }
}

dependencies {
  implementation('com.futurae.sdk:futuraekit:3.x.x') //or :futuraekit-beta for beta versions
}

Configuration and Initialization

To configure and initialize the SDK, take the following steps:

Add Permissions

Please add the following permissions, which the FuturaeKit SDK needs, if they are not already present in your AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

Initialize the SDK

SDK Configuration Options

The SDK can be configured to provide a fine-tuned mode of operation with regards to user presence verification and security, using the following configuration parameters. For a detailed explanation of how user presence verification works for these configurations, please refer to user presence verification section.

Lock configuration type

setLockConfigurationType() - This function sets the user presence verification lock configuration type between the following options: NONE, BIOMETRICS_ONLY, BIOMETRICS_OR_DEVICE_CREDENTIALS and SDK_PIN_WITH_BIOMETRICS_OPTIONAL

Unlock duration

setUnlockDuration() - Sets the duration in seconds that the SDK remains unlocked for, after successfully calling an unlock method. The accepted value range is between 2 and 300 seconds, and configuring any value out of this range will raise an exception. If the unlock duration is not set, then the SDK will assume a default value of 60 seconds.

Biometric invalidation

setInvalidatedByBiometricChange() - If set to true, adding a new biometric credential on the device will result in the user presence verification mechanism being invalidated, rendering most of the SDK functions unavailable depending on the lock configuration type. For a detailed explanation, please refer to user-presence-verification section.

Unlocked device required

setUnlockedDeviceRequired() - if set to true, the device must be unlocked to perform cryptographic operations. Otherwise operations will fail even if the SDK is unlocked. This defaults to false

Skip hardware security

setSkipHardwareSecurity() - if set to true, the SDK will not store cryptographic material on the device’s strongbox. This defaults to false meaning if this is not set, the SDK will always attempt to use the strongbox if available on the device.

SDK Initialization

We recommend using an android Application class to initialize the SDK. If you already have one in your app, follow these steps:

Firstly, in your Application class find or create the onCreate method and add the following code to initialize the FuturaeKit SDK:

import com.futurae.sdk.FuturaeSDK

class App : Application() {

  override fun onCreate() {
    super.onCreate();

    val config = SDKConfiguration.Builder()
      .setLockConfigurationType(config)
      .setUnlockDuration(unlockDuration)
      .setInvalidatedByBiometricChange(true)
      .setUnlockedDeviceRequired(true)
      .setSkipHardwareSecurity(false)
      .build()
    try {
      //launch SDK with the specified configuration
      FuturaeSDK.launch(this, config)   
    } catch (e : Exception) {
      when(e) {
        // Indicates that SDK is already initialized
        is FTInvalidStateException -> { ... }
        // Indicates that provided SDK configuration is invalid
        is FTLockInvalidConfigurationException -> {... }
        // Indicates that provided SDK configuration is valid but cannot be supported on this device
        is FTLockMechanismUnavailableException -> { ... }
        // Indicates that an SDK cryptographic operation failed or cryptographic material is missing
        is FTKeystoreException -> { ... }
        // Indicates that migration from a previous SDK installation failed.
        // You can attempt to recover the SDK via Accounts recovery. Ref error below.
        is FTMigrationFailedException, 
        // Indicates that the SDK is in a corrupted state and should attempt to launch for account recovery
        // Keep in mind the SDK is NOT initialized until recovery is completed successfully  
        is FTCorruptedStateException -> {
          // Refer to section SDK Recovery for implementation 
          val sdkPin = if(requiresPin) "1234".toCharArray() else null
          attemptSDKRecovery(config, sdkPin)
        }
      }
    }
}

Once the SDK is initialized, you may access the SDK instance and functions using the SDK client getter:

FuturaeSDK.client

Secondly, in your res/values folder make sure to create a file futurae.xml with the following contents:

<?xml version="1.0" encoding="utf-8"?>
<resources>

  <string name="ftr_sdk_id">{FuturaeSdkId}</string>
  <string name="ftr_sdk_key">{FuturaeSdkKey}</string>
  <string name="ftr_base_url">https://api.futurae.com:443</string>

</resources>

You can get your SDK credentials from Futurae Admin:

  • First you need to choose or determine the Futurae Service which is configured in your backend application.
  • Assuming you can access the particular Service on Futurae Admin, you can go to Settings -> Mobile SDK in order to get your SDK credentials
  • If Mobile SDK is not enabled for the particular Service, please contact support@futurae.com in order to enable it.

SDK Initialization errors & recovery

Errors can occur during SDK initialization, either due to SDK misconfiguration or device failures. For a full list of errors, refer to FuturaeSDK.launch.

  • FTInvalidStateException: this exception during the SDK initialization suggests that the SDK was already initialized. It is recommended to check if this SDK launch method in your app code is called after the SDK is already initialized. In this case you might be able to access the SDK instance with FuturaeSDK.getClient().
  • FTLockMechanismUnavailableException: This exception indicates that the device does not support strong biometrics. Resetting the SDK will not resolve this issue. This problem may occur on specific devices that do not meet Class 3 (formerly Strong) biometrics specification by Android. In such cases, it is recommended to handle the exception by avoiding biometrics for these devices and using the SDK PIN as the user presence verification mechanism instead. Refer to user presence verification section for all supported options.
  • FTLockInvalidConfigurationException: This exception typically occurs when the SDK is initialized with an incorrect combination of parameters. In this scenario, it is advised to double-check the SDK configuration with the SDK Configuration Options section.
  • FTKeystoreException: This exception during initialization indicates that the Keystore fails to generate key material. The SDK’s setSkipHardwareSecurity feature may help in some of these exception cases.
  • FTCorruptedStateException: This exception occurs when the SDK’s cryptographic material or encrypted storage become corrupted. Some corrupted cases may be recoverable using the SDK’s FuturaeSDK.launchAccountRecovery method. If the SDK cannot recover the corrupted material, it must be reset, and existing accounts will need to be re-enrolled. There is a maximum of 5 recovery attempts allowed per SDK installation.
  • FTMigrationFailedException: Indicates that the SDK failed to migrate a previous installation of SDK v1.x.x to the format of SDK v2.x.x or later. Please call FuturaeSDK.reset
SDK Recovery

When receiving FTCorruptedStateException during initialization, you can attempt to recover the SDK via launchAccountRecovery. Below is a sample code snippet that shows how to recover the SDK for every available LockConfigurationType. If the SDK cannot recover the corrupted material, it must be reset, and existing accounts will need to be re-enrolled.

fun attemptSDKRecovery(sdkConfiguration: SDKConfiguration, sdkPin: CharArray?) {
  val userPresenceVerificationMode = when (sdkConfiguration.lockConfigurationType) {
    NONE -> null
    BIOMETRICS_ONLY -> WithBiometrics(
      PresentationConfigurationForBiometricsPrompt(
        fragmentActivity,
        "SDK Recovery",
        "Authenticate to recover",
        "Authenticate to recover",
        "Cancel",
      )
    )

    BIOMETRICS_OR_DEVICE_CREDENTIALS -> WithBiometricsOrDeviceCredentials(
      PresentationConfigurationForDeviceCredentialsPrompt(
        fragmentActivity,
        "SDK Recovery",
        "Authenticate to recover",
        "Authenticate to recover",
      )
    )

    SDK_PIN_WITH_BIOMETRICS_OPTIONAL -> sdkPin?.let {
      WithSDKPin(it)
    }
      ?: throw IllegalStateException("Unable to recover without SDK PIN for provided SDKConfiguration")
  }
  FuturaeSDK.launchAccountRecovery(
    application,
    sdkConfiguration,
    userPresenceVerificationMode,
    object : Callback<Unit> {
      override fun onSuccess(result: Unit) {
        onSDKLaunched(sdkConfiguration)
      }

      override fun onError(throwable: Throwable) {
        showErrorAlert("SDK Recovery failed", throwable)
      }
    }
  )
}

Configure R8 and ProGuard

For maven installations of the SDK, consumerProguardFiles are used by the SDK to provide the proguard rules automagically to your app. Nothing more needs to be done.

Demo App

The SDK’s repository contains a stripped-down demo app which showcases examples of how some of the SDK’s core features can be integrated. For beta versions, such as the current SDK v3, please refer to the SDK beta repository.

Features

The FuturaeKit SDK for Android provides the following features and functionality:

Public API Overview

The Public SDK API is grouped by functionality into several sections as illustrated below. The purpose of having groups is to express similarity in behaviour of a collection of methods. Some methods not belonging to a group are accessible from the root API.

FuturaeSDK
├── client
│   ├── authApi
│   │   ├── approve
│   │   ├── reject
│   │   ├── getOfflineQRVerificationCode
│   │   ├── getNextSynchronousAuthToken
│   │   └── getTOTP
│   ├── migrationApi
│   │   ├── getMigratableAccounts
│   │   └── migrateAccounts
│   ├── lockApi
│   │   ├── unlock
│   │   ├── lock
│   │   ├── isLocked
│   │   ├── getActiveUnlockMethods
│   │   ├── activateBiometrics
│   │   ├── deactivateBiometrics
│   │   ├── haveBiometricsChanged
│   │   ├── switchToLockConfiguration
│   │   └── changeSDKPin
│   ├── sessionApi
│   │   ├── getSessionInfo
│   │   └── extractQRCodeExtraInfo
│   ├── accountApi
│   │   ├── getAccountHistory
│   │   ├── getAccountsStatus
│   │   ├── enrollAccount
│   │   ├── logoutAccount
│   │   ├── logoutAccountOffline
│   │   ├── getActiveAccounts
│   │   ├── getAccount
│   │   └── registerFirebasePushToken
│   ├── operationsApi
│   │   ├── handleUri
│   │   ├── handlePushNotification
│   │   ├── decryptPushNotificationExtraInfo
│   │   └── getIntegrityVerdict
│   └── adaptiveApi
│       ├── enableAdaptive
│       ├── disableAdaptive
│       ├── isAdaptiveEnabled
│       └── collectAndSubmitObservations
├── launch
├── launchAccountRecovery
└── reset

FTQRCodeUtils
├── getUserIdFromQrcode
├── getSessionTokenFromQrcode
└── getQrcodeType

FTUriUtils
├── getUserIdFromUri
└── getSessionTokenFromUri

FTNotificationUtils
├── getApproveSession
├── getUserId
├── getEncyptedExtras
└── getDeviceId 

BackupAgent
├── onBackup
└── onRestore

Standard workflow

FuturaeSDK object is a main entry point to SDK operations. The standard host app workflow would be to:

  1. Initialize SDK via the FuturaeSDK.launch(...) method and handle launch errors if appropriate.
  2. If launch is successful (doesn’t throw any errors), get the instantiated SDK client using FuturaeSDK.client.
  3. Run the desired flows using the APIs that the SDK client object provides.

Notes:

  • FuturaeSDK is a singleton object. It never changes.
  • FuturaeSDK.client object can only be acquired after successful SDK initialization.

SDK top level methods

  1. launch - start SDK (and have FuturaeSDK.client instance created) for the given configuration.
  2. launchAccountRecovery - attempt to mitigate problems related to keystore failures by recovering part of SDK secrets, if SDK essential cryptographic material is available.
  3. reset - erase all SDK data. This can be used to clean the SDK instance before initializing the SDK again.

AuthApi

AuthApi groups futurae SDK client-facing methods used to authenticate the user with the supported factors.

It offers the following methods:

  1. approve - Approves an authentication session.
  2. reject - Rejects authentication session.
  3. getOfflineQRVerificationCode - Generates a 6-digit OTP that the user needs to enter in the browser, when authenticating with the offline QR Code factor.
  4. getNextSynchronousAuthToken - Retrieves synchronous JWT authentication token. If successful, this call increments the synchronous authentication counter.
  5. getTOTP - Returns a 6 digit mobile TOTP provided user id. This TOTP can be used to authenticate the user through the passcode authentication factor.

MigrationApi

MigrationApi is a collection of methods to deal with Automatic Account Recovery related tasks.

  1. getMigratableAccounts - Retrieves MigratableAccounts that can be migrated and respective migration requirements.
  2. migrateAccounts - Runs account migration for all eligible accounts.

LockApi

LockApi provides an SDK interface to manage user presence verification.

  1. unlock - unlocks SDK, using user presence verification method depending on SDK configuration.
  2. lock - locks the SDK, resulting in the next protected operation to require a successful user presence verification (if required by the lock configuration).
  3. isLocked - returns the current lock state of the SDK.
  4. getActiveUnlockMethods - returns the list of methods that can be used to verify user presence (unlock the SDK).
  5. activateBiometrics - activates biometric unlock method for the SDK_PIN_WITH_BIOMETRICS_OPTINAL lock configuration.
  6. deactivateBiometrics - deactivates biometric unlock method for the SDK_PIN_WITH_BIOMETRICS_OPTINAL lock configuration.
  7. haveBiometricsChanged - returns true if device unlock biometrics configuration changed.
  8. changeSDKPin - updates pin code for SDK_PIN_WITH_BIOMETRICS_OPTINAL lock configuration.

SessionApi

SessionApi contains methods to get session information and extract encrypted session information if needed.

  • getSessionInfo - retrieves session information based on the provided sessionId or sessionToken.
  • extractQRCodeExtraInfo - extracts the session’s extra-info from an offline QR code.

AccountApi

AccountsApi is a collection of methods to handle user accounts. It includes methods to enroll and log out an account, get and update information related to user accounts.

  1. getAccountHistory - gets Account history for the selected userId.
  2. getAccountsStatus - gets accounts statuses for given userIds.
  3. enrollAccount - enrolls user account based on input params. This is a single entry-point for all enrollment operations.
  4. logoutAccount - logs out a user account and deletes it from the SDK.
  5. getActiveAccounts - gets a list of the currently enrolled accounts.
  6. getAccount - returns an account identified by provided query, if any.
  7. logoutAccountOffline - deletes an account from the SDK.
  8. registerFirebasePushToken - registers a device push notification token on the Futurae backend.

Operations API

OperationsApi is a collection of methods that complement other flows and may optionally be used, depending on the specific goals.

  1. handleUri - handles auth and enrollment URIs.
  2. getIntegrityVerdict - gets an IntegrityResult verdict about the integrity of the app, app-license and device.
  3. decryptPushNotificationExtraInfo - get decrypted extras of the session.
  4. handlePushNotificaition - act on the incoming push notification sent by Futurae’s backend.

Adaptive API

AdaptiveApi contains methods to leverage the Adaptive SDK. To use it, add the Adaptive SDK dependency in your project.

  • enableAdaptive - enables the Adaptive SDK to perform adaptive collections and adaptive authentications.
  • disableAdaptive - disables the Adaptive SDK to stop performing adaptive collections and adaptive authentications.
  • isAdaptiveEnabled - returns true if Adaptive SDK is enabled.
  • collectAndSubmitObservations - perform an adaptive collection ad-hoc.

Utils and helpers

  1. FTUriUtils an object with utility methods to work with URIs.
  2. FTQRCodeUtils - an object with helper methods to work with QR codes issued by Futurae.
  3. FTNotificationUtils an object with helper methods to work with LocalBroadcasts coming from SDK.

Threading model

Futurae SDK has both synchronous (immediate return) operations and asynchronous (return when ready) operations. The asynchronous operations in its turn can signal its completion or return a value.

For asynchronous operations, the SDK implements the paradigm with the AsyncCompletableOperation and AsyncOperation<T>.

interface AsyncOperation<T> {
    suspend fun await(): T
    fun addCallback(callback: Callback<T>)
}

interface AsyncCompletableOperation {
    suspend fun await()
    fun addCallback(callback: CompletionCallback)
}

Notes:

  • AsyncOperations are designed to be called once. When an operation is constructed it does not start execution immediately. The execution starts when either the await() or addCallback(...) method is called on the operation.
  • For callbacks via the addCallback(...) method, the SDK will call completion on the main (UI) thread. It is the app’s responsibility to move the thread to background or to another thread of choice.
  • For coroutines, the hosting app implementation may choose a thread where result is returned via coroutines API. SDK in its turn, operates on the background thread and will move operations there, regardless of what thread operation it was started from.

Example with enrollment

Let’s assume this operation as a common start for both callback or coroutines flow:

val enrollmentOperation = FuturaeSDK.client.accountApi.enrollAccount(
    EnrollmentParams(
        EnrollmentInput.ActivationCode("provided activation code"),
    )
)

For coroutines, one way to interact with AsyncCompletableOperation would be the following:

// use withContext if you want result returned on the context of your choice.
suspend fun handlingFunction() {
  try {
     enrollmentOperation.await()
  } catch (t: Throwable) {
      // handle enrollment errors
  }

  // enrollment operation completed
}

For callbacks:

fun handlingFunction() {
   enrollmentOperation.addCallback(object : CompletionCallback {
      override fun onComplete() {
          // react
      }

      override fun onError(error: Throwable) {
          // handle error
      }
  })
}

Push notifications

Your app must be set up to receive Firebase Cloud Messaging (FCM) push notifications from the Futurae backend. You can choose to receive and handle these notifications yourself, or alternatively you can use the existing infrastructure provided in the SDK. You can find more information on how to setup FCM push notifications for your app in the Firebase Cloud Messaging Developer Guide.

In order to be able to receive FCM notifications, you need to specify the Firebase Messaging service inside the application section of your Manifest:

<service android:name="com.futurae.sdk.messaging.FTRFcmMessagingService">
  <intent-filter>
    <action android:name="com.google.firebase.MESSAGING_EVENT" />
  </intent-filter>
</service>

For this purpose, you can either use the one included in the SDK (com.futurae.sdk.messaging.FTRFcmMessagingService), or write your own. This service overrides two important methods:

override fun onNewToken(token: String)

override fun onMessageReceived(message: RemoteMessage)

The first one is invoked whenever a new FCM push token has been generated for the app; it is important to register this token with the Futurae backend in order to continue receiving push notifications. The FTRFcmMessagingService implements this functionality.

The second one is invoked whenever a new push notification (or cloud message) is received by the app. The FTRFcmMessagingService then processes this message and invokes the SDK accordingly.

FCM token registration

The FTRFcmMessagingService is responsible for registering the app’s FCM token to the Futurae server. This is important for the server to be able to issue FCM notifications for your app. The provided service handles this, however if you need to, you can write your own or extend the existing one.

If you are implementing your own FCM notification handling, you should register the FCM token to the Futurae server every time it changes. The call that registers the FCM token to the Futurae server is registerFirebasePushToken() , and it is necessary every time the FCM token is generated or is changed by FCM.

For example, once the app receives a new FCM token (e.g. via an onNewToken() callback in your own FirebaseMessagingService subclass), the token needs to be obtained and registered to the Futurae server using the following code:

private val job = SupervisorJob()
private val coroutineScope = CoroutineScope(Dispatchers.Default + job) 

override fun onNewToken(token: String) {
    super.onNewToken(token)
    coroutineScope.launch {
      val sdkStatus = FuturaeSDK.sdkState().value.status
      if (sdkStatus is FuturaeSDKStatus.Uninitialized || sdkStatus is FuturaeSDKStatus.Corrupted) {
        // Persist token or flag for upload, once SDK is initialized
        return@launch
      }
      try {
        FuturaeSDK.client.accountApi.registerFirebasePushToken(token)
      } catch (e : Throwable) {
        // upload failed. Handle errors and retry
      }
  }
}

FCM listener service

The FTRFcmMessagingService receives FCM push notifications and handles them, according to the actions dictated by the Futurae server. You can use or extend the service provided by the SDK, or write your own. There are two distinct push notification types issued by the Futurae server: Aprove or Unenroll.

In case you want to process and handle the FCM notifications without using FTRFcmMessagingService, you must use the following code in order to process and handle the notifications sent by the Futurae server, inside the implementation of your own FirebaseMessagingService subclass:

override fun onMessageReceived(message: RemoteMessage) {
    super.onMessageReceived(message)
    coroutineScope.launch {
      val sdkStatus = FuturaeSDK.sdkState().value.status
      if (sdkStatus is FuturaeSDKStatus.Uninitialized || sdkStatus is FuturaeSDKStatus.Corrupted) {
          // Make sure to initialize SDK if need be
      }
      val messageData = message.data
      FuturaeSDK.client.operationsApi.handlePushNotification(messageData)
    }
}

Local intents

Once a Futurae FCM notification has been handled, the SDK will notify the host app using local broadcasts. The app should register a broadcast receiver for these intents and react accordingly. There are three distinct Intents that the notification handlers might send in a local broadcast:

  • INTENT_GENERIC_NOTIFICATION_ERROR: Indicates that an error was encountered during the processing or handling of a FCM notification.
  • INTENT_APPROVE_AUTH_MESSAGE: Indicates that a One-Touch authentication attempt has been initiated.
  • INTENT_ACCOUNT_UNENROLL_MESSAGE: Indicates that a user account has been logged out remotely.
  • INTENT_QRCODE_SCAN_REQUESTED: Indicates that a QR code was issued for this device (see QR easy scan).

The following example shows how to register for these intents:

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(FTNotificationUtils.INTENT_GENERIC_NOTIFICATION_ERROR);  // General notification error intent
intentFilter.addAction(FTNotificationUtils.INTENT_APPROVE_AUTH_MESSAGE);        // Approve authentication notification intent
intentFilter.addAction(FTNotificationUtils.INTENT_ACCOUNT_UNENROLL_MESSAGE);    // Logout user notification intent
intentFilter.addAction(FTNotificationUtils.INTENT_QRCODE_SCAN_REQUESTED);    // QR code scan intent

LocalBroadcastManager.getInstance(getContext()).registerReceiver(new BroadcastReceiver() {

  @Override
  public void onReceive(Context context, Intent intent) {

    switch(intent.getAction()) {
      case FTNotificationUtils.INTENT_ACCOUNT_UNENROLL_MESSAGE:
        // Handle unenroll notification (e.g. refresh lists)
        break;

      case FTNotificationUtils.INTENT_APPROVE_AUTH_MESSAGE:
        // Handle approve notification (e.g. show approve view)
        break;

      case FTNotificationUtils.INTENT_GENERIC_NOTIFICATION_ERROR:
        // Handle FCM notification error
        break;

      case FTNotificationUtils.INTENT_QRCODE_SCAN_REQUESTED:
        // handle QR code scan request
        break;
    }
  }
}, intentFilter);

Encrypted push notification extras

The Futurae Authentication platform provides a mechanism to encrypt the extra information that is provided in the context of a Push authentication or transaction signing session. When encrypted, the sessions’s extra information is encoded as a base64 string of an encrypted JSON.

The SDK function decryptPushNotificationExtraInfo receives a push notification intent produced by the SDK and returns a List<ApproveInfo> which is a json representing the extras.

 private val broadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        when (intent.action) {
            FTNotificationUtils.INTENT_APPROVE_AUTH_MESSAGE -> {
                 val hasExtraInfo =
                        intent.getBooleanExtra(FTNotificationUtils.PARAM_HAS_EXTRA_INFO, false)
                    
                    if (!hasExtraInfo) {
                        Timber.e("Nothing to handle")
                        return
                    }
                    
                    val userId = FTNotificationUtils.getUserId(intent)
                    val extras = intent.getStringExtra(FTNotificationUtils.PARAM_ENCRYPTED_EXTRA)
                    
                    if (userId == null || extras == null) {
                        Timber.e("Make sure encrypted extras are enabled for your service")
                        return
                    }

                    val decryptedExtras = try {
                        FuturaeSDK.client.operationsApi.decryptPushNotificationExtras(userId, extras)
                    } catch (e: FTUnlockRequiredException) {
                        Timber.e("Please unlock SDK to get extras: ${e.message}")
                        null
                    } catch (e: FTAccountNotFoundException) {
                        Timber.e("Account not found for provided user id: ${e.message}")
                        null
                    } catch (e: FTEncryptedStorageCorruptedException) {
                        Timber.e("Encrypted storage corrupted. Please use account recovery operation: ${e.message}")
                        null
                    } catch (e: FTKeystoreOperationException) {
                      Timber.e("Keystore operation failed: ${e.message}")
                      null
                    }?.let { 
                        // Use serialization library of your choice. The type is JSONArray.
                        Gson().toJson(it)
                    }


                // Use extra information
            }
            FTNotificationUtils.INTENT_ACCOUNT_UNENROLL_MESSAGE -> {
                //...
            }
            FTNotificationUtils.INTENT_APPROVE_CANCEL_MESSAGE -> {
                //...
            }
            FTNotificationUtils.INTENT_GENERIC_NOTIFICATION_ERROR -> {
                //...
            }
        }
    }
}

User presence verification

User presence verification enhances security by requiring the Futurae SDK to be unlocked by verifying user presence, before certain protected SDK operations can be performed. User presence can be verified by leveraging Class 3 Strong biometrics (e.g., fingerprint, iris, or face recognition), device credentials (e.g. PIN, pattern, or password) or an SDK PIN (also known as Custom App PIN), set by the user upon the first enrollment, with the purpose of verifying the user identity to unlock the SDK methods that are protected by the user presence verification mechanism.

The Futurae SDK provides a set of options which can be configured during the Initialize the SDK step, in order to control the behavior of the user presence verification mechanism.

setLockConfigurationType() - This function sets the user presence verification lock configuration type between the following options:

  • NONE - The SDK will not use any lock mechanism, therefore user presence verification will not be enforced. This is the default value in case that a lock configuration type is not set. This option must be used when launching the SDK for the first time, after migrating from a previous SDK v1.x.x install.
  • BIOMETRICS_ONLY - This lock configuration type allows the end user to only unlock the SDK through strong biometric authentication.
  • BIOMETRICS_OR_DEVICE_CREDENTIALS - The SDK can be unlocked by authenticating either using biometrics or device credentials.
  • SDK_PIN_WITH_BIOMETRICS_OPTIONAL - Allows the user to perform user presence verification by authenticating with an SDK-specific PIN or with biometrics. In this case, the user is required to set an SDK PIN (upon the first enrollment), which will be stored and verified on the server side. On top of that, the user may choose to enable and use biometric authentication, as a means of convenience. This SDK configuration implies a server-side validation of the SDK PIN, even when using biometric verification, therefore the device must have network connectivity. Nevertheless, when configured with this lock configuration type, the SDK provides functionality which can be used to authenticate the user even when the device is offline, such as the methods describe in the sections TOTP and offline QR code.

setUnlockDuration() - Sets the duration in seconds that the SDK remains unlocked for, after successfully calling an unlock method. The accepted value range is between 2 and 300 seconds, and configuring any value out of this range will raise an exception. If the unlock duration is not set, then the SDK will assume a default value of 60 seconds.

setInvalidatedByBiometricChange() - If set to true, adding a new biometric credential on the device will result in the user presence verification mechanism being invalidated, rendering most of the SDK functions unavailable. The steps to reinstate the user presence verification mechanism vary according to the configured lock configuration type:

  • NONE - doesn’t support user presence verification.
  • SDK_PIN_WITH_BIOMETRICS_OPTIONAL - The app can either re-activate biometrics or reset and initialize the SDK again.
  • For the remaning lock configuration types, the only option is to reset and initialize the SDK.

Verify user presence with biometrics

The Futurae SDK can be unlocked by performing biometrics verification. After calling the unlock() method with UserVerificationMode.WithBiometrics, the OS will display a biometric prompt to authenticate the user and, if the verification is successful, the SDK will be unlocked for the configured period.

The unlock(UserPresenceVerificationMode.WithBiometrics(…)) method can be used when the SDK configured lock mechanism type is BIOMETRICS_ONLY or SDK_PIN_WITH_BIOMETRICS_OPTIONAL. When the SDK configured lock mechanism is SDK_PIN_WITH_BIOMETRICS_OPTIONAL, a server-side verification of the SDK PIN takes place, therefore network connectivity is required. Upon succesful unlock, the SDK will return the type of UserPresenceVerificationFactor indicating the way user-presence was verified.

// suspend context
try {
  FuturaeSDK.client.lockApi.unlock(
    WithBiometrics(
      presentationConfiguration = PresentationConfigurationForBiometricsPrompt(
        // activity that presents system biometrics prompt
        fragmentActivity = requireActivity(),
        promptTitle = "Unlock SDK",
        promptSubtitle = "Authenticate with biometrics",
        promptDescription = "Authentication is required to unlock SDK operations",
        negativeButtonText = "cancel"
      )
    )
  ).await()
  // user presence successfully verified
} catch (t: Throwable) {
  //user presence verification failed
}

Verify user presence with biometrics or device credentials

if the SDK setLockConfigurationType is set as BIOMETRICS_OR_DEVICE_CREDENTIALS, user presence can be verified using biometrics, providing that the SDK biometrics verification is active. If biometrics verification is not active, the SDK will automatically proceed with user presence verification using the configured device credentials, such as the device PIN.

The method lockApi.unlock when called with WithBiometricsOrDeviceCredentials will attempt to verify user presence preferentially using biometrics, when the conditions described in the section SDK biometrics configuration are met, or alternatively using the configured device credentials. Upon succesful unlock, the SDK will return the type of UserPresenceVerificationFactor indicating the way user-presence was verified.

try {
  FuturaeSDK.client.lockApi.unlock(
    WithBiometricsOrDeviceCredentials(
      presentationConfiguration = PresentationConfigurationForDeviceCredentialsPrompt(
        // activity that presents system biometrics prompt
        fragmentActivity = requireActivity(),
        promptTitle = "Unlock SDK",
        promptSubtitle = "Authenticate with biometrics",
        promptDescription = "Authentication is required to unlock SDK operations",
      )
    )
  ).await()
  // user presence successfully verified
} catch (t: Throwable) {
  //user presence verification failed
}

Verify user presence with SDK PIN or biometrics

User presence can also be verified using an SDK PIN, which is an alphanumeric password that the user configures in the app by the time that the first account is activated, and it may then be used as a user presence verification mechanism.

In this case, the app needs to implement the relevant mechanism to ask the user to supply the SDK PIN and then invoke the function lockApi.unlock with WithSDKPin parameter providing PIN supplied by the user. After successfully verifying the SDK PIN, the SDK will remain unlocked for the configured unlock period.

Unlocking the SDK with an SDK PIN is only available if the configured lock configuration type is SDK_PIN_WITH_BIOMETRICS_OPTIONAL and after the user complete the first enrollment and setup the SDK PIN.

This unlock method requires a server-side verification of the SDK PIN, therefore the device must have network connectivity for the operation to be completed successfully. Moreover, the user can choose to enable the SDK biometrics authentication, as a means of convenience. When the SDK biometrics verification is enabled, user presence can be additionally verified using biometrics, besides using the SDK PIN. Upon succesful unlock, the SDK will return the type of UserPresenceVerification indicating the way user-presence was verified.

try {
  FuturaeSDK.client.lockApi.unlock(
      // pass pin code received from the user
      WithSDKPin("pin_code")
  ).await()
  //user presence successfully verified
} catch (t: Throwable) {
  //user presence verification failed
}
SDK PIN entry constraints

As the SDK PIN is verified by the Futurae backend, the constraints described below are imposed on the server side.

  1. The SDK PIN validation is limited to 5 consecutive failed attempts, and the at the 5th consecutive failed attempt the SDK will enter a permanent locked state.

  2. The failed SDK PIN entry attempts are consecutive. A user may enter a wrong pin 4 times without consequences. If the correct SDK PIN is entered during the 5th attempt, the failed attempts counter is reset to 0.

  3. After a successful SDK PIN validation, the SDK PIN failed attempts counter is reset to 0.

  4. The SDK does not enforce specific SDK PIN patterns or restrictions, since the PIN policies can be enforced by the host app.

SDK locked due to consecutive SDK PIN failed attempts

When the SDK is locked due to failed SDK PIN attempts:

  • The SDK is permanently locked and cannot be used again.
  • The user device in the Futurae backend is archived and cannot be used to authenticate the user anymore.
  • Once locked due to failed SDK PIN attempts, the SDK will need to be reset.
  • The user will need to re-enroll all previously enrolled accounts.

The following SDK exceptions related to SDK PIN can be caught and handled by the hosting app:

  • FTApiPinNotNeededException
    • With error 40000: When the SDK PIN is provided but not required.
  • FTApiPinMissingException
    • With error 40061: Indicates that the SDK PIN is missing or empty.
  • FTApiPinIncorrectException
    • With error 40164: If the user enters an incorrect SDK PIN for the 1st time, this code will be triggered. It also means that there are 4 more attempts left to enter the correct SDK PIN.
    • With error 40163: When the user enters an incorrect SDK PIN for the 2nd time. It also informs that there are 3 more attempts left to enter the correct SDK PIN.
    • With error 40162: When the user enters an incorrect SDK PIN for the 3rd time. It also informs that there are 2 more attempts left to enter the correct SDK PIN.
    • With error 40161: When the user enters an incorrect SDK PIN for the 4th time. It also informs that there is 1 more attempt left to enter the correct SDK PIN.
  • FTApiDeviceArchivedException
    • With error 41002: When the SDK PIN validation fails for the 5th consecutive time, thus the SDK is permanently locked.

Lock SDK

It is advised that the app locks the SDK after an operation finishes as well as when the app is in a state where it would have its security compromised, for instance when it is moved to the background. The SDK may be immediately locked by calling the lockApi.lock() function.

FuturaeSDK.client.lockApi.lock()

Performing a successful user verification will unlock the SDK for the configured unlockDuration passed as an argument during initialization. It’s recommended that the app locks the SDK right after the completing the protected operation to which it was unlocked for, instead of waiting the configure unlockDuration to expire leading the SDK to be automatically locked.

Once locked, the SDK will need to be unlocked again via user presence verification as described above and depending on the selected lock configuration type.

Check if the SDK is locked

At any point in time you can check the locked status of the SDK by calling the method isLocked().

if (FuturaeSDK.client.lockApi.isLocked()) {
  //Unlock SDK before calling a protected method
}

Get SDK unlock methods

The SDK provides functionality for the app to query the available methods which can be used to perform user verfication and consequently unlock the SDK. Calling the method getActiveUnlockMethods returns a list of UnlockMethodType enum values that the SDK can currently be unlocked with:

FuturaeSDK.client.lockApi.getActiveUnlockMethods()

enum UnlockMethodType {
     BIOMETRICS,
     BIOMETRICS_OR_DEVICE_CREDENTIALS,
     SDK_PIN
}

Switch SDK configuration

The SDK provides a way to switch to a different SDK configuration post initialization via the following method.

The target configurations available are:

  • None
  • Biometrics
  • BiometricsOrCredentials
  • PinWithBiometricsOptional

QR codes

The Futurae Authentication platform, uses QR codes for different operations:

  • Enroll a device for a user (online)
  • Authenticate a user or session (online)
  • Authenticate a user or session (offline)
  • Approve or reject an authentication session not attached to a specific user (usernameless)

The Futurae SDK supports all these functionalities. The business App can have different flows for supporting the needed functionality, or condense all QR Code operations in one. The Futurae SDK provides a handy method to identify on the fly which type of QR Code the user has scanned:

when (FTQRCodeUtils.getQrcodeType(qrcodeValue)) {
    FTQRCodeUtils.QRType.Enroll -> // enroll
    FTQRCodeUtils.QRType.Offline -> // compute verification code from QR
    FTQRCodeUtils.QRType.Online -> // fetch session and approve
    FTQRCodeUtils.QRType.Usernameless -> // select an account, fetch session and approve
}

Trusted Session Binding

Trusted Session Binding is a security enhancement that strengthens SDK operations through additional authentication by utilizing binding tokens, reducing unauthorized access and enhancing security without compromising user experience. Refer to Enroll with trusted session binding and Automatic Account Recovery on how to use this feature. Refer to the Auth API docs for session binding on how to get a binding token.

Enroll

To enroll this SDK as a device for a Futurae User, there are two options. Enrollment via an activation QR code and enrollment via an activation shortcode.

Enroll with QR code

To enroll with an enrollment QR code, call the enrollAccount() method using a valid activation code, for example obtained by scanning an enrollment QR Code:

startActivityForResult(FTRQRCodeActivity.getIntent(this), FTRQRCodeActivity.RESULT_BARCODE);

If a QR Code is successfully scanned then onActivityResult will be called, with the scanned code:

@Override
protected fun onActivityResult(requestCode: Int, resultCode: Int, Intent data) {

  if (resultCode == RESULT_OK && requestCode == FTRQRCodeActivity.RESULT_BARCODE) {

    //you may use this or any other function to scan the QR code
    val qrcode : Barcode = data.getParcelableExtra(FTRQRCodeActivity.PARAM_BARCODE)

    //first, make sure the SDK is unlocked, then proceed

    FuturaeSDK
      .client
      .accountApi
      .enrollAccount(EnrollmentParams(ActivationCode(qrcode.rawValue)))
      .addCallback(object : CompletionCallback {
          override fun onComplete() {
              // enrollment completed
          }

          override fun onError(error: Throwable) {
              // enrollment did not complete successfully
          }
      })
  }

}

Please make sure to call enroll, inside the onActivityResult method to complete the user enrollment.

Enroll with activation shortcode

To enroll with an activation shortcode, the same method has to be called with a ShortActivationCode input.

FuturaeSDK
    .client
    .accountApi
    .enrollAccount(EnrollmentParams(ShortActivationCode(code)))
    .addCallback(object : CompletionCallback {
        override fun onComplete() {
            // enrollment completed
        }

        override fun onError(error: Throwable) {
            // enrollment did not complete successfully
        }
    })

Enroll and setup the SDK PIN

When the SDK lock configuration type is SDK_PIN_WITH_BIOMETRICS_OPTIONAL, the user must set the SDK PIN upon the first enrollment. To do that, enrollAccount method has to be called with the EnrollmentUseCase.EnrollAccountAndSetupSDKPin(sdkPin).

The app must implement the necessary mechanism to ask the user to insert the SDK PIN. Currently Futurae does not perform any validation on the quality of the supplied PIN. The app is assumed to ensure that the PIN meets the security standards as defined by the customer app logic and requirements (for example: PIN “1111” or “1234” is not allowed).

FuturaeSDK
  .client
  .accountApi
  .enrollAccount(
      EnrollmentParams(
        // qr code raw value
        inputCode = ActivationCode(qrcode.rawValue),
        // sdkPin provided by the user
        enrollmentUseCase = EnrollAccountAndSetupSDKPin(sdkPin)
      ),
  )
  .addCallback(object : CompletionCallback {
      override fun onComplete() {
        // an account was enrolled and the SDK is unlocked
      }

      override fun onError(error: Throwable) {
        // handle enrollment failure
      }
  })

Enroll with trusted session binding

When Trusted Session Binding is enabled for enrollment, the app is additionally expected to provide a valid binding token for the enrollment. The enrollment process thus comprises two parts: first, a custom procedure to authenticate against the host app backend and retrieve the binding token, and second, a Futurae enrollment.

  1. Custom authentication. The process of retrieving the binding token is specific to the app and is independent of Futurae. As part of this process the app performs the custom pre-agreed authentication steps with the host app backend and retrieves a binding token for the scope of the enrollment (refer to Auth API docs for session binding docs for reference on how the host app backend generates and retrieves the binding token from Futurae). The scope of the enrollment is defined as the enrollment activation code, so it is important that the app shares it with the host app backend during this initial authentication process.
  2. Futurae enrollment. To enroll, pass the binding token as the EnrollmentParams.flowBindingToken parameter to the Futurae enrollAccount method. For example:
FuturaeSDK.client.accountApi.enrollAccount(
    EnrollmentParams(
        inputCode = ActivationCode(qrcode.rawValue),
        enrollmentUseCase = EnrollAccount,
        flowBindingToken = token
    )
).await()

Logout (Unenroll)

To remove an account from this SDK (ie unenroll this user device), call the protected method logoutAccount():

//first, make sure the SDK is unlocked, then proceed
FuturaeSDK.client.accountApi.logoutAccount(userId)
  .addCallback(object : CompletionCallback {
    override fun onComplete() {
      // handle logout success (eg. refresh accounts list)
    }

    override fun onError(error: Throwable) {
      // handle logout failure
    }
  })

Typically this should happen either when the user removes the account manually from the app, or when a user has been logged out remotely by the server. In the former case, calling the logoutAccount() method is enough, as it notifies the server of the logout and deletes the account from the SDK too. In the latter case, the server will send a FCM notification to the app, and the default handler of the notification will delete the account from the SDK as well. In this case, the notification handler will also send a local broadcast to the app (see INTENT_ACCOUNT_UNENROLL_MESSAGE above, so that the app can also perform any further action required (e.g. refresh the list of active accounts in an account view).

The intent contains the Futurae user_id, as well as the device_id, as an extra:

val userId = FTNotificationUtils.getUserId(intent)
val deviceId = FTNotificationUtils.getDeviceId(intent)

To manually delete an account from the local database, which is needed for instance if you handle the FCM notifications without using FTRFcmMessagingService (see the Push notifications section) and an unenroll push notification is received, call the logoutAccountOffline() method:

FuturaeSDK.client.accountApi.logoutAccountOffline(userId)

SDK biometrics configuration

The Futurae SDK allows the user to configure the device biometrics as a convenient way to verify the user presence and unlock the SDK. Once biometrics verification is enabled, it will become the first choice to perform the user presence verification.

The SDK may be unlocked using biometrics, given that the following conditions are met:

  • The SDK is configured with LockConfigurationType.BIOMETRICS_OR_DEVICE_CREDENTIALS, LockConfigurationType.SDK_PIN_WITH_BIOMETRICS_OPTIONAL or LockConfigurationType.BIOMETRICS_ONLY (in this last case, biometrics verification is mandatory as it is the only method available to unlock the SDK).
  • If the SDK is configured with LockConfigurationType.SDK_PIN_WITH_BIOMETRICS_OPTIONAL, then biometrics verification must be activated at the SDK level. To do so, the function activateBiometrics() must be called. Note that only for this lock configuration type the app needs to activate biometrics verification at the SDK level. For all the other lock configuration types, biometrics verification will automatically be used to unlock the SDK, as long the user has biometrics configured as a lock mechanism to unlock the device.
  • The SDK biometric keys are valid. If the SDK configuration setInvalidatedByBiometricChange is set as true, in order for the SDK keys to be valid the following conditions must be verified:
    • No new biometrics were configured. If the user configures a new biometric entry in the device settings, the SDK biometrics will become invalid.
    • At least one of the biometrics remain configured since the last time that the SDK was initiatilized or the SDK biometrics were activated.
  • The user has at least one biometric verification option configured in the device settings.

Activate SDK biometrics verification

To activate the SDK biometrics verification the app needs to call the protected function activateBiometrics(), after verifying the user presence to unlock the SDK.

The activateBiometrics method is only available for the SDK_PIN_WITH_BIOMETRICS_OPTIONAL lock configuration type. If called right after enrolling the first account and setting up the SDK PIN with the method enroll(enrollmentUseCase = EnrollmentUseCase.EnrollAccountAndSetupSDKPin), and before the unlockDuration expires, the app may call this function without having to unlock the SDK. This is particularly useful for apps that want to enforce biometrics as a user presence verification method.

The following example may be used for instance when the user enables biometrics verification in the app settings:

//first, make sure the SDK is unlocked with SDK PIN verification, then proceed
FuturaeSDK.client.lockApi.activateBiometrics(
  PresentationConfigurationForBiometricsPrompt(
    // the activity that will present system biometrics prompt
    requireActivity(),
    promptTitle = "Unlock SDK",
    promptSubtitle = "Activate biometrics",
    promptDescription = "Authenticate to enable biometric authentication",
    negativeButtonText = "Cancel"
  )
).addCallback(object : CompletionCallback {
  override fun onComplete() {
    // The SDK biometrics are enabled
  }

  override fun onError(error: Throwable) {
    // handle biometric activation failure
  }
})

Deactivate SDK biometrics verification

If the user wishes to deactivate biometrics verification, the app may use the protected deactivateBiometrics() function to revoke biometric authentication as an option to unlock the SDK.

Being a protected function, deactivateBiometrics may only be called after verifying the user presence using SDK PIN.

//first, make sure the SDK is unlocked with SDK PIN verification, then proceed
FuturaeSDK.client.lockApi.deactivateBiometrics()
  .addCallback(object : CompletionCallback {
    override fun onComplete() {
      // handle successful biometrics deactivation
    }

    override fun onError(error: Throwable) {
      // handle failed biometrics deactivation
    }
  })

Check if the SDK biometrics are valid

Specifically when the SDK is configured with setInvalidatedByBiometricChange(true), if new biometric credentials are configured in the device or in case that the user removes all the configured biometrics credentials, the SDK biometric verification will be automatically revoked and the user will have to explicitly activate it again using activateBiometrics as described above or reset the SDK if the SDK is configured with LockConfigurationType is BIOMETRICS or BIOMETRICS_OR_DEVICE_CREDENTIALS.

To check if the device biometric credentials have changed, the app may call the haveBiometricsChanged() method:

if (FuturaeSDK.client.lockApi.haveBiometricsChanged()){
  // the device biometrics have changed. To reactivate, call activateBiometrics()
}

Change SDK PIN

When the SDK LockConfigurationType is SDK_PIN_WITH_BIOMETRICS_OPTIONAL, the SDK PIN configured upon the first enrollment, may be updated with the protected function changeSDKPin() as shown next:

// first, make sure the SDK is unlocked with SDK PIN verification, then proceed
FuturaeSDK.client.lockApi.changeSDKPin(
    newSdkPin = "new_pin"
).addCallback(object: CompletionCallback {
  override fun onComplete() {
    // handle successful SDK PIN change
  }

  override fun onError(error: Throwable) {
    // handle failed SDK PIN change
  }
})

Account status

To get a list of all enrolled accounts in this SDK, call the getActiveAccounts() method:

val accounts: List<FTAccount> = FuturaeSDK.client.accountApi.getActiveAccounts()

In order to get a specific FTAccount, call the functions getAccount with the desired AccountQuery specified.

//get the FTAccount for the provided userId
val account: FTAccount? = FuturaeSDK.client.accountApi.getAccount(AccountQuery.WhereUserId(userId))

//get the FTAccount for the provided userId and deviceId
val account: FTAccount? = FuturaeSDK.client.accountApi.getAccount(AccountQuery.WhereUserIdAndDevice(userId, deviceId))

To fetch the status and pending authentication Authentication session information for these accounts, you can use the following method (where userIds is a list of the corresponding user IDs):

FuturaeSDK.client.accountApi.getAccountsStatus("id1", "id2")
  .addCallback(object : Callback<AccountsStatus> {
    override fun onSuccess(result: AccountsStatus) {
      // handle pending sessions
      for (accountStatus in result.statuses) {
        for (pendingSession in accountStatus.activeSessions) {
          // pendingSession.sessionId
        }
      }
    }

    override fun onError(error: Throwable) {
      // handle an error
    }
  })

Account History

Protected function to get a list of all authentications for an enrolled account in the SDK, call the getAccountHistory method:

val accountHistory: List<AccountHistoryItem> = try {
  FuturaeSDK.client.accountApi.getAccountHistory("id1").await()
} catch(t: Throwable) {
  // failed to get account history
}

Authenticate user

One-Touch

When a One-Touch (aka Approve) session starts, the server will send a push notification to the app, where the user should approve or reject the authentication session. The notification is received and handled by the SDK, which in turn will send a local broadcast (see INTENT_APPROVE_AUTH_MESSAGE above), so that the app can perform any required actions. For example, the app might want to display a prompt to the user so that they can approve or reject the session.

The intent contains an object that describes the authentication session as an extra. This object contains the user ID and the session ID, which are required for sending the authentication outcome to the server:

val authSession: ApproveSession? = FTNotificationUtils.getApproveSession(intent)
authSession?.userId
authSession?.sessionId

Once the outcome of the authentication has been decided by the app, it should be sent to the server for the authentication session to complete.

Approve authentication

To approve the authentication session, when no extra_info is supplied in Authenticate with One-Touch, use the approve() method. This is a protected method so the SDK must be first unlocked according to the configured SDK lockConfigurationType.

The following example demonstrates how to approve an authentication session when the SDK is unlocked by verifying user presence using biometrics:

// coroutines are used to avoid nested callbacks. Callbacks can be used too with the same result.

try {
  FuturaeSDK.client.lockApi.unlock(
    WithBiometrics(
      presentationConfiguration = PresentationConfigurationForBiometricsPrompt(
        // the activity that will present system biometrics prompt
        requireActivity(),
        promptTitle = "Unlock SDK",
        promptSubtitle = "",
        promptDescription = "Authenticate to approve transaction",
        negativeButtonText = "Cancel"
      )
    )
  ).await()

  val sessionApprovalParameters =
  ApproveParameters.Builder(
      SessionId(userId, sessionId)
  ).build()

  FuturaeSDK
    .client
    .authApi
    .approve(sessionApprovalParameters)
    .await()
} catch (t: FTException) {
    // handle failures
    return
}
Approve with Multi-Numbered Challenge

If Multi-Numbered Challenge is enabled for your Futurae Service, SessionInfo objects returned by the getSessionInfo method will have multiNumberedChallenge field populated with a selection of numbers for the user to choose.

try {
  val sessionInfo = FuturaeSDK
    .client
    .sessionApi
    .getSessionInfo(
        UserSessionQuery(ById("session_id"), userId = "user_id")
    ).await()

    // present user with a number selection from the sessionInfo.multiNumberedChallenge
    // The user will know which one is correct, 
    // because it's shown on the application where transaction was initialized.

    val usersSelectedChoice = 42 // selected by the user

    val approveAuthParameters = ApproveParameters.Builder(
      SessionId(
        "userId", sessionInfo.sessionId
      )
    )
      .setMultiNumberedChallengeChoice(usersSelectedChoice)
      .build()

    FuturaeSDK
      .client
      .authApi
      .approve(approveAuthParameters)
      .await()
    
    // approval successfully completed
} catch (t: FTException) {
    // handle failure
}

It is up to the client application to present user with a choice of numbers. User’s choice has to be passed back to the approve method. To approve a user authentication with multi-numbered challange when extraInfo field has a non-null value (refer to authentication session information), include extra info to the approveAuthParameters via the builder method setExtraInfo.

Backend installation receives the correct choice in the response to the authentication call. The correct choice is unknown (not sent) to the SDK. To let user know of the correct choice, it has to be displayed on the application where transaction was initialized.

Reject authentication

The user might choose to reject the authentication session. Additionally, in case the session has not been initiated by the user, they might also choose to report a fraudulent authentication attempt back to the server. In this case, and when no extra_info is supplied in Authenticate with One-Touch, use the following example to unlock the SDK, as this is a protected function, and reject an authentication with the reject() method:

// it can be done via callbacks with the same result. Coroutines are used to avoid nested callbacks.
try {
  FuturaeSDK
    .client
    .lockApi
    .unlock(WithSDKPin("user_pin"))
    .await()

  val rejectParameters = RejectParameters.Builder(
      SessionId(
        //Futurae account's user id which the authentication was created for
        "userId",
        "sessionId"
      )
    )
      .setRejectReply(Deny)
      .build()

  FuturaeSDK.client.authApi.reject(
    rejectParameters
  ).await()

  // session successfully rejected

} catch (t: FTException) {
  // handle failure
}

The default session reply type is Deny. Alternatively, fraud can be reported by passing the ReportFraud via the setRejectReply method of the RejectParameters.Builder.

Online QR code

The online QR Code authentication factor consists in using the authenticator app to scan a QR Code provided by the server.

To complete the authentication, provide the content of the online QR Code scanned by the user to the protected function approve(), according to the following example:

try {
  //use the approriate unlock parameters, matching the current LockConfigurationType.
  FuturaeSDK
    .client
    .lockApi
    .unlock(WithSDKPin("user_pin"))
    .await()

  val approveParameters = ApproveParameters.Builder(
    OnlineQR(
        "raw qr code content",
    )
  ).build()

  FuturaeSDK.client.authApi.approve(
    approveParameters
  ).await()

  // session successfully approved
} catch (t: FTException) {
  // handle failure
}
QR easy scan

Futurae SDK can inform the host app when an online QR code authentication is requested for one of the enrolled accounts. This request is received on the SDK through a push notification message that uses LocalBroadcasts to relay the message to the host app as described in the Local intents section. This intent is then handled by the app.

The flow works the following way:

  1. An Auth API is invoked by the customer backend to start QR code authentication, with qr_code factor. Auth Api reference.
  2. A push notification is sent to the SDK instances where the user account, for which the authentication was started for, is enrolled.
  3. SDK spawns a specific local broadcast with action FTNotificationUtils.INTENT_QRCODE_SCAN_REQUESTED.
  4. The host app receives the broadcast via LocalBroadcastManager and handles it accordingly.

Notes:

  • It is assumed that the SDK successfully receives push notifications. To ensure that, the host app needs to follow FCM token registration procedure and correctly implements push notification handling as described FCM listener service section.
  • For best flexibility handling the QR scan push, it’s recommended that LocalBroadcastReceiver is initialized in the main Application class. That way, when the app is not running, if a push notification is received, Application class starts first, followed by CloudMessagingService which will receive the push notification and allow the broadcast to handle it. The host app is responsible for implementing the QR code scanning screen that is presentented when the SDK passes a signal to the host app via local broadcast.
  • To complete the authentication, provide the scanned QR code data to the SDK approve function as described in the Online QR code authentication section.

Offline QR code

Offline QR code allows the user to authenticate by scanning a QR code, without the need of an internet connection on the authenticator device.

To authenticate with the offline QR code Factor, scan the QR code provided by the server and display the relevant information to the user. If the user approves the authentication or transaction request, the app may use the following methods to generate the 6-digit verification code that the user needs to enter in the browser.

In the case of the SDK_PIN_WITH_BIOMETRICS_OPTIONAL lock configuration type, since the SDK PIN is being validated server side, the app needs to act differently on functions that can take place while offline.

Specifically, getOfflineQRVerificationCode called with mode = SDKAuthMode.Unlock is a protected function that requires prior unlocking of the SDK. However, for the case that the app is offline the SDK offers the SDKAuthMode.PinOrBiometrics in which case operation is not protected but require either passing the SDK PIN as argument or authenticating with biometrics in-place.

The method getOfflineQRVerificationCode() generates the verification code based on the user authentication mode.

// unlock SDK first. May require internet access for pinCode, so there is a separate method for pinCode.
val result = FuturaeSDK.client.authApi.getOfflineQRVerificationCode("qrCodeContent", mode = SDKAuthMode.Unlock).await()

// for SDK_PIN_WITH_BIOMETRICS_OPTIONAL prior unlock is not required. But caller has to specify how will user be authenticated offline.
val result = FuturaeSDK.client.authApi.getOfflineQRVerificationCode(
  "qrCodeContent",
  mode = SDKAuthMode.PinOrBiometrics(PinCode("user PIN"))
).await()

// prior unlock not required. Biometric authentication will be used in-place
val result = FuturaeSDK.client.authApi.getOfflineQRVerificationCode(
  "qrCodeContent",
  mode = SDKAuthMode.PinOrBiometrics(
      Biometrics(
          presentationConfigurationForBiometricsPrompt = PresentationConfigurationForBiometricsPrompt(...)
  )
)).await()
// To extract the session's extra info use the following method.
val extraInfo = FuturaeSDK.client.sessionApi.extractQRCodeExtraInfo(qrCode)

Usernameless QR Code

To approve or reject a usernameless QR code session, first scan the QR Code provided by the server, then fetch the session info via the QR Code’s token and finally approve or reject it with an enrolled user’s id using the approve() method.

try {
  val sessionInfo = FuturaeSDK
    .client
    .sessionApi
    .getSessionInfo(
      SessionInfoQuery(
        ByToken(sessionToken),
        userId,
      )
    ).await()

  val approveParameters = ApproveParameters.Builder(
    UsernamelessQR(
      "userId",
      "qr_code_content"
    )
  ).build()

  // set extra-info if applicable
  sessionInfo.approveInfo?.let {
    approveParameters.setExtraInfo(it)
  }

  // or reject
  FuturaeSDK.client.authApi.approve(
      approveParameters
  ).await()

} catch (t: FTException) {
  // handle error
}

Authentication session information

For a given authentication session, either identified via a session ID (e.g. received by a push notification) or a session Token (e.g. received by a QRCode scan), you can ask the Futurae backend for more information about the session, using the protected method getSessionInfo. The parameter of the method specifies information that identifies session depending on what’s provided.

// first, make sure the SDK is unlocked, then proceed

// if you have a session ID
val sessionInfo = try {
  FuturaeSDK.client.sessionApi.getSessionInfo(
    UserSessionQuery(ById("session_id"), userId = "user_id")
  ).await()
} catch (t: Throwable) {
  // handle error
}

// if you have a session Token
val sessionInfo = try {
  FuturaeSDK.client.sessionApi.getSessionInfo(
    UserSessionQuery(ByToken("session_token"), userId = "user_id")
  ).await()
} catch (t: Throwable) {
  // handle error
}

If there is extra information to be displayed to the user (supplied when calling Authenticate User or Authenticate Transaction in the backend API), for example when confirming a transaction, this will be indicated with a list of key-value pairs in the extra_info part of the response.

In order to query the server for the authentication session information, you need the user ID and the session ID or session token. You can use the following helper methods to obtain these from either a URI or a QR Code:

object FTUriUtils {
  fun getUserIdFromUri(uri: String): String?
  fun getSessionTokenFromUri(uriStr: String?): String?
}

object FTQRCodeUtils {
  fun getUserIdFromQrcode(qrCode: String): String
  fun getSessionTokenFromQrcode(qrCode: String): String
}
Authentication with extra information

In case it exists, you can retrieve the extra information that the user needs to review while authenticating by getting the approveInfo of the SessionInfo object:

val sessionInfo: SessionInfo
val approveInfo: List<ApproveInfo> = sessionInfo.approveInfo

Each ApproveInfo object is a key-value pair that describes a single piece of information; call the following methods to retrieve the key and value of the object respectively.

val approveInfo: ApproveInfo
approveInfo.key
approveInfo.value

IMPORTANT: In case the authentication contains extra_info, it is mandatory that the information is fetched from the server, displayed to the user, and included in the authentication response in order for it to be included in the response signature. Otherwise, the server will reject the authentication response. Make sure to include extra_info via the ApproveParameters.Builder, or RejectParameters.Builder, setExtraInfo method.

ApproveParameters.Builder(
  SessionId(
    userId = "userId",
    sessionId = "qr_code_content"
  )
).setExtraInfo(extras).build()

RejectParameters.Builder(
  SessionId(
    userId = "userId",
    sessionId = "qr_code_content"
  )
).setExtraInfo(extras).build()

End-to-end encryption

Futurae provides an optional mechanism to encrypt session extra information. When this feature is enabled, the authentication session extra information is encrypted in transit. Afterwards, when fetching the session information via the SDK sessionAPI.getSessionInfo, the session’s extra information is decrypted by the SDK, and returned to the app in plain-text. It’s also possible to fetch a session extra information from other sources, such as encrypted push notification extras or Offline QR code, for more details refer to the respective sections.

TOTP

TOTP authentication can be used for offline authentication, as there is no requirement for an internet connection in the app.

The protected method getTOTP is used to get the current TOTP, when the SDK LockConfigurationType is other than SDK_PIN_WITH_BIOMETRICS_OPTIONAL:

//first, make sure the SDK is unlocked, then proceed
val totp = FuturaeSDK.client.authApi.getTOTP("user_id", SDKAuthMode.Unlock).await()

// use the resulting object
totp.passcode
totp.validityPeriodDurationSeconds
totp.remainingSeconds

As seen in this example, the getTOTP() method returns an object that contains the TOTP itself, but also some information about the validity of the object. When validity period is over, a new TOTP must be obtained by the SDK.

When the SDK LockConfigurationType is SDK_PIN_WITH_BIOMETRICS_OPTIONAL, and the user has no biometrics verification enabled, the user presence is then validated using the SDK PIN. In that case the getTOTP method may be used to authenticate when the user has no internet connection, by adding the the SDK PIN argument. No prior unlock required.

val totp =
  FuturaeSDK.client.authApi.getTOTP(
    "user_id",
    SDKAuthMode.PinOrBiometrics(PinCode("123".toCharArray()))
  )
  .await()

When the SDK is configured with SDK_PIN_WITH_BIOMETRICS_OPTIONAL, if biometrics verification is activated, the TOTP may be generated using the getTOTP() method, by passing the SDKAuthMode.PinOrBiometrics(Biometrics(...)).

val totp =
  FuturaeSDK.client.authApi.getTOTP(
    "user_id",
    SDKAuthMode.PinOrBiometrics(
      Biometrics(
        presentationConfigurationForBiometricsPrompt = PresentationConfigurationForBiometricsPrompt(
          requireActivity(),
          "Unlock SDK",
          "Authenticate with biometrics",
          "Authentication is required to unlock SDK operations",
          "cancel"
        )
      )
    )
  )
  .await()

Synchronous Authentication Token

Protected function that creates an OTP based authentication token that can be used with the Authenticate with Synchronous Auth endpoint for synchronous authentication. To get a sychronous authentication token for an enrolled user, use the getNextSynchronousAuthToken method:

val synchronousToken = FuturaeSDK.client.authApi.getNextSynchronousAuthToken("userId")

As a result of the above method, the mobile SDK will return a synchronous authentication token that can be used to authenticate the user. The token is returned to the mobile app, that has to submit it to the customer backend, which in turn needs to provide the token to the Authenticate with Synchronous Auth Auth API endpoint, in order to authenticate the user.

Users already enrolled will also be able to perform synchronous authentication. After migrating to an SDK version that supports this factor, once the app is launched, the SDK will add the sync factor to the device’s capabilities, provided that the device has stable internet connection, otherwise if it fails to update the sync capability the SDK will try again upon each launch until a successful update.

URI schemes

The SDK is able to handle URI scheme calls, which can be used to either enroll or authenticate users. Once your activity has been set up to handle the URI scheme call intents, get the intent data in the onCreate() method of your activity, which contains the URI that should be passed in the SDK, using the protected method handleUri():

//first, make sure the SDK is unlocked, then proceed
FuturaeSDK.client.operationsApi.handleUri(uriString).addCallback(object : CompletionCallback {
  override fun onComplete() {
    // operation is complete
  }

  override fun onError(error: Throwable) {
    // ...
  }
})

IMPORTANT: In case you perform authentication with a URI scheme call (using the mobile_auth_uri value which is returned by the Futurae backend when calling Authenticate User or Authenticate Transaction), and this authentication includes extra information to be displayed to the user, you must retrieve this information from the server and include it in the authentication response. See section Authentication session information for details on how to do that.

Automatic Account Recovery

Automatic account recovery (aka account migration) allows the user to migrate the Futurae account(s) currently enrolled in an existing Futurae Android SDK installation to a different device, or to the same one after erasing and reinstalling an app. This mechanism migrates the accounts using data which were backed up by the Android OS when the previous app installation had enrolled account(s). Hence, it enables the user to recover their accounts on a new app installation automatically.

On the fresh app installation, the SDK is able to identify if account migration is possible and return the accounts which can be migrated. In order to do so, invoke the method getMigratableAccounts().

val migratableAccountsInfo: MigratableAccounts = try {
  FuturaeSDK.client.migrationApi.getMigratableAccounts().await()
} catch (t: Throwable) {
  // handle error
  return
}

if (migratableAccountsInfo.pinProtected) {
    // Unlock if needed and proceed with FuturaeSDK.client.migrationApi.migrateAccounts(MigrationUseCase.AccountsSecuredWithPinCode("123".toCharArray()))
} else {
    // Unlock if needed and proceed with FuturaeSDK.client.migrationApi.migrateAccounts(MigrationUseCase.AccountsNotSecuredWithPinCode)
}

To execute account migration, invoke the function migrateAccounts() with an appropriate MigrationUseCase as argument, depending on the SDK’s configuration of the previous and current app installations:

  • If the accounts were protected by an SDK PIN code on the previous SDK installation or should be protected by an SDK PIN code in the current SDK installation, use MigrationUseCase.AccountsSecuredWithPinCode("pin_code".toCharArray()). If the accounts to be migrated were previously enrolled with SDK PIN, then the pin provided to this use-case must be the same as the old one. If the current SDK lock configuration is SDK_PIN_WITH_BIOMETRICS_OPTIONAL, a prior SDK unlock will not be required and upon a successful migration call the SDK will be unlocked.
  • If the accounts were not protected by an SDK PIN, use MigrationUseCase.AccountsNotSecuredWithPinCode.

When Trusted Session Binding is enabled for account recovery, the app is additionally expected to provide a valid binding token for the account recovery. The account recovery process thus comprises two parts: first, a custom procedure to authenticate against the host app backend and retrieve the binding token, and second, a Futurae account recovery.

  1. Custom authentication. The process of retrieving the binding token is specific to the app and is independent of Futurae. As part of this process the app performs the custom pre-agreed authentication steps with the host app backend and retrieves a binding token for the scope of the account recovery (refer to Auth API docs for session binding docs for reference on how the host app backend generates and retrieves the binding token from Futurae). The scope of the account recovery is defined as the device IDs of the migratable accounts, which are available through the method getMigratableAccounts(). It is important that the app shares them with the host app backend during this initial authentication process.
  2. Futurae account recovery. For account recovery, pass the binding token as flowBindingToken parameter to the migrateAccounts() method.
// flowBindingToken is a nullable optional parameter to use Trusted session binding as an additional security measure.
// if migratableAccountsInfo.pinProtected == true, ask user for pin code and pass it to the migrateAccounts function.
try {
  FuturaeSDK
    .client
    .migrationApi
    .migrateAccounts(
      MigrationUseCase.AccountsSecuredWithPinCode("pin_code".toCharArray()),
      flowBindingToken,    
    ).await()
} catch (t: Throwable) {
  // handle migration failure
}

// if migratableAccountsInfo.pinProtected == false, use AccountsNotSecuredWithPinCode.
FuturaeSDK.client.migrationApi.migrateAccounts().await()
try {
  FuturaeSDK
    .client
    .migrationApi
    .migrateAccounts(
      MigrationUseCase.AccountsNotSecuredWithPinCode,
      flowBindingToken,
    ).await()
} catch (t: Throwable) {
  // handle migration failure
}

IMPORTANT: In order for account migration to be feasible on the new device, the following are required:

  • The migration data of the SDK in the previous app installation must have been previously backed up, so that the SDK in the fresh app installation can retrieve them. See Enabling SDK Backup and Restore for instructions on how to achieve this.
  • The SDK in the new app installation must have no other accounts currently or previously enrolled.

You may catch exceptions thrown by the checkAccountMigrationPossible() and executeAccountMigration() methods by catching com.futurae.sdk.AccountsMigrationException or for specific cases the following exceptions that inherit from it:

Enabling SDK Backup and Restore

In order for the Futurae Android SDK to be able to include the necessary data in the Android backup, as well as to retrieve them from an existing device backup, you need to create a BackupAgent class (extending android.app.backup.BackupAgent) and declare it in your AndroidManifest.xml file as follows:

<application
  android:allowBackup="true"
  android:backupAgent="<PATH_TO_BACKUPAGENT_CLASS>">

In your BackupAgent class make sure to override onBackup(...) and onRestore(...) like in the following example. If you want to backup and restore only Futurae related data, invoke the onBackupAccounts() inside onBackup(...) and onRestoreAccounts() inside onRestore(...). On the other hand, if you have your own data to backup and restore, then during onRestore(...) iterate the keys inside BackupDataInput and pass the entity data to the SDK’s onRestoreAccountsData(...):

@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) {
  try {
    // App-specific backup logic, if you have any, goes here...
    // BackupAgent imported from com.futurae.sdk package
    BackupAgent.onBackupAccounts(this, oldState, data, newState);
  } catch (IOException e) {
    // Handle exception
  }
}

@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
  try {
    // If your app backs up data that you want to restore in your own code by iterating data keys/values,
    // then all the data your app code doesn't handle must be passed to com.futurae.sdk.BackupAgent.onRestoreAccountsData(Context,BackupDataInput).
    // For a detailed implementation example, check our public demo app.
    //
    // Otherwise let the SDK handle the migration entirely by BackupAgent.onRestoreAccounts().
    // BackupAgent imported from com.futurae.sdk package
    BackupAgent.onRestoreAccounts(this, data, appVersionCode, newState);
  } catch (IOException e) {
    // Handle exception
  }
}

The signatures of the two methods are the following:

public static void onBackupAccounts(Context context, ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException
public static void onRestoreAccounts(Context context, BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException

Adaptive

Using state-of-the-art technology, Adaptive reduces fraud risk by analyzing numerous device-inherit and environmental signals simultaneously to build a user-specific secure context.

Permissions

Before enabling the Adaptive feature, make sure the following permissions are present in your app’s AndroidManifest.xml and requested during runtime:

<!-- Runtime permissions -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission
    android:name="android.permission.BLUETOOTH"
    android:maxSdkVersion="30" />
<uses-permission
    android:name="android.permission.BLUETOOTH_ADMIN"
    android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- For Android 13 or higher -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />

<!-- Static permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

Usage

In order to enable the adaptive mechanism, add the extra adaptive-sdk dependency to your app/build.gradle file. Refer to the android-adaptive-sdk github repository for the latest release.

repositories {
    maven {
      name = "GitHubPackages"
      url = "https://maven.pkg.github.com/Futurae-Technologies/android-adaptive-sdk"
      credentials {
        username = GITHUB_ACTOR
        password = GITHUB_TOKEN
      }
    }
}

dependencies {
   implementation com.futurae.sdk:adaptive-sdk:x.x.x
}

To enable or disable the Adaptive SDK, call the corresponding method after having initialized the FuturaeSDK:

class App : Application() {

    override fun onCreate() {
        super.onCreate()
        FuturaeSDK.launch(
            this, SDKConfiguration.Builder()
                .setLockConfigurationType(config)
                .setUnlockDuration(unlockDuration)
                .setInvalidatedByBiometricChange(true)
                .setUnlockedDeviceRequired(true)
                .setSkipHardwareSecurity(false)
                .build()
        )
    }

    fun enableAdaptive() {
        FuturaeSDK.client.adaptiveApi.enableAdaptive(this)
    }


    fun disableAdaptive() {
        FuturaeSDK.client.adaptiveApi.disableAdaptive()
    }

    fun isAdaptiveEnabled() : Boolean {
        FuturaeSDK.client.adaptiveApi.isAdaptiveEnabled()
    }
}

As long as Adaptive SDK is enabled, context collections will occur regularly. The data collected depends on the runtime permissions which were granted to the app as well as the device’s Quick Settings. For instance, location data will not be available if the user’s device has the location setting turned off. Refer to the Permissions section for more.

User Risk Radar

User Risk Radar is an Adaptive solution that provides an effective mechanism to reduce the risk of fraudulent authentication or transaction signing attempts. For further details on how this feature works, refer to the User Risk Radar guide.

To use this feature, follow the instructions described on the sections Permissions and Usage. Additionally, contact support@futurae.com to enable User Risk Radar for your Futurae Service.

Adaptive Account Recovery

Adaptive Account Recovery provides the same functionality as Automatic Account Recovery but with an additional security layer that validates if the user is on a trusted context, before allowing the accounts to be recovered. For more details about this feature refer to the Adaptive Account Recovery guide.

Whenever a user attempts an adaptive account recovery in a new app installation (or reinstallation), the Futurae SDK performs a fresh scan to gather the available contextual data of the new device, and subsequently compares it with the data which has been previously observed for the specific user. If the new device is in a so-called trusted context, then the account migration request is deemed safe and hereby allowed. If the context is not known, the request is denied and the account recovery process is not allowed to happen automatically. Instead the user should be prompted to contact the Customer’s support desk, in order to perform an assisted activation of the new device.

To use this feature, follow the instructions described on the sections Permissions and Usage. Additionally, contact support@futurae.com to enable Adaptive Account Recovery for your Futurae Service.

Adaptive Account Recovery is executed using the same methods getMigratableAccounts() and migrateAccounts() as Automatic Account Recovery.

Errors

If Adaptive is enabled, then calling migrateAccounts() may also return one of the following Adaptive errors:

Public key pinning

Public key pinning is by default enabled in Futurae Android SDK so that it only accepts a specific list of public keys when connecting to the Futurae backend over TLS. No further configuration is necessary. The SDK further restricts allowed TLS ciphers to OkHttp RESTRICTED_TLS.

Reset SDK data

The SDK provides a convenient method to clear all SDK internal data, including accounts, cryptographic keys and secrets, as well as the lock configuration. After successfully calling the reset() function, all the enrolled accounts will be removed, and they will need to be enrolled again if needed.

The SDK may be reset in the following situations:

Integrity Verdict

The SDK can detect the integrity of the App and Device by calling getIntegrityVerdict function. This function returns an object of IntegrityResult which contains a verdict about the device, application and application’s channel of acquisition. For example, you can perform the following check to detect a rooted device or emulator:

val integrityVerdict = try {
    FuturaeSDK.client.getIntegrityVerdict().await()
} catch (t: Throwable) {
    // handle error
    return
}

if (integrityVerdict.deviceVerdicts.contains(DeviceVerdict.MEETS_NO_INTEGRITY)) {
    // ...
}

SDK protected functions

Some of the SDK functions are protected with an extra security layer, therefore they can only be called by the app when the SDK is in an unlocked status. In order to unlock the SDK, user presence must be verified depending on the SDK lockConfigurationType (see User presence verification section).

SDK protected functions

SDK unlock functions

The SDK provides unlock functions that when called, user presence is verified, and if successful the SDK is unlocked for the configured unlockDuration. The unlock functions which may be used, depend on the configured lockConfigurationType (see User presence verification section).

SDK has one unlock function unlock that accepts necessary arguments depending on the desired user presence verification mode. If successful, SDK transitions to unlocked state.

Unlock function parameters
  • userPresenceVerificationMode: User presence verification mode for unlocking.
  • shouldWaitForSDKSync: optional Boolean value (false by default) specifying to wait until all necessary internal SDK synchronisation operations complete before returning. This may increase the unlocking operation time, but will prevent encountering FTSDKBlockedException errors during SDK operations immediately after unlocking, for example in case the SDK has to perform network updates with the Futurae server.

The enrollAccount function unlocks SDK if enrollment is successful when enrollmentUseCase is EnrollAccountAndSetupSDKPin.

// unlock API
FuturaeSDK.client.lockApi.unlock(
  userPresenceVerificationMode = WithSDKPin(sdkPin),
  shouldWaitForSDKSync = true
).await()

// enroll API with SDK PIN
FuturaeSDK
  .client
  .accountApi
  .enrollAccount(
    EnrollmentParams(
      // either ActivationCode or ShortActivationCode
      inputCode = ActivationCode(qrcode.rawValue),
      // if usecase is EnrollAccountAndSetupSDKPin, then SDK will be unlocked if enrollment is successful.
      enrollmentUseCase = EnrollAccountAndSetupSDKPin(sdkPin)
    ),
  )
  .addCallback(object : CompletionCallback {
    override fun onComplete() {
        // enrollment completed
    }

    override fun onError(error: Throwable) {
        // enrollment did not complete successfully
    }
  })

SDK State

SDK v3 introduces a Kotlin StateFlow method that exposes FuturaeSDKState, consisting of the following parameters:

  • status is a sealed class FuturaeSDKStatus with subclasses for the following states:
    • Uninitialized when the SDK is not yet launched.
    • Locked when the SDK is Locked
    • Unlocked when the SDK is unlocked.
    • Corrupted when the SDK detects storage or keystore inconsistencies. This type contains an Integer errorCode about the type of corruption, a Boolean isRecoverable showing whether account recovery can be used and the Throwable throwable that caused this.
  • isBlocked boolean value showing whether the SDK is blocked by an operation updating accounts such as during enrollment or during syncing SDK public key with the server. While this flag is true, you cannot perform other account based operations
  • remainingUnlockedTime integer value reflecting the remaining unlocked time in seconds.
//usage example
lifecycleScope.launch {
  FuturaeSDK.sdkState().collect {
    when (it.status) {
      is FuturaeSDKStatus.Corrupted -> { 
          // reset or perform account recover
      }
      FuturaeSDKStatus.Locked -> {
          // unlock and perform your operations
      }
      FuturaeSDKStatus.Uninitialized -> {
          // launch SDK 
      }
      is FuturaeSDKStatus.Unlocked -> {
          // perform your operation
      }
    }
    if (it.isBlocked) {
      // wait until the SDK is unblocked if you want to perform an operation.
    } 
    // update ui with unlock time left
    unlockTimeLeft.text = "${it.remainingUnlockedTime}"
  }
}

Error handling

The Android SDK version 3 offers a reworked our Error model based on the following principles:

  • All errors are present under the package com.futurae.sdk.public_api.exception, have prefix FT (renamed from v2) and extend FTException which extends IOException
  • There is a class hierarchy which makes it easier to handle errors on high level or low level. For example, FTKeystoreException is a superclass, with multiple child classes for individual errors such as FTKeyNotFoundException for missing keys or FTKeystoreOperationException for keystore operation failures. You can handle these type of system errors as granular as you choose.
  • Every FuturaeSDK API interface function, is documented with @throws for every error that it can throw or return via callback, along with a short description of why it happened.
  • For the full list of errors, please refer to the respective public_api.exception package

Error hierarchy

FTException : IOException
├──FTCorruptedStateException
│  └──FTEncryptedStorageCorruptedException
├──FTKeystoreException
│  ├──FTKeystoreOperationException
│  ├──FTKeyNotFoundException
│  ├──FTSignatureException
│  └──FTAlgorithmUnavailableException
├──FTLockException
│  ├──FTLockInvalidConfigurationException
│  ├──FTUnlockRequiredException
│  ├──FTLockMechanismUnavailableException
│  ├──FTLockAuthenticationFailedException
│  └──FTLockBiometricsInvalidatedException
├──FTRecoveryException
│  ├──FTRecoveryMaxAttemptsExceededException
│  └──FTRecoveryImpossibleException
├──FTAccountsMigrationException
│  ├──FTAccountsMigrationUnexpectedException
│  ├──FTAccountsMigrationPinRequiredException
│  ├──AccountsMigrationFTAccountsPreviouslyEnrolledException
│  ├──AccountsMigrationFTAccountsExistException
│  └──FTAccountMigrationNoMigrationInfoException
├──FTApiException
│  └── ...
├──FTMalformedQRCodeException
├──FTAccountNotFoundException
├──FTInvalidStateException
├──FTInvalidFormatException
├──FTInvalidOperationException
├──FTInvalidArgumentException
├──FTSDKBlockedException
└──FTMigrationFailedException

Usage example

Let’s take getAccountsStatus from accountAPI as an example. This operation can fail due to:

  • Invalid SDK state (Blocked by an operation)
  • Corrupted encrypted storage
  • System keystore errors
  • API may reject the request
protected fun getAccountsStatus() {
        lifecycleScope.launch {
            FuturaeSDK.client.accountApi.getActiveAccounts().takeIf { it.isNotEmpty() }
                ?.let { accounts ->
                    try {
                        FuturaeSDK.client.accountApi.getAccountsStatus(*accounts.map { it.userId }
                            .toTypedArray())
                    } catch (t: Throwable) {
                        when (t) {
                            // SDK is blocked by an accounts related operation. Please wait and try again.
                            is FTSDKBlockedException -> {
                                ...
                            }
                            // Encrypted storage is corrupted. You should attempt account recovery to fix your SDK installation
                            is FTEncryptedStorageCorruptedException -> {
                                ...
                            }
                            // System keystore error. Reset SDK
                            is FTKeystoreException -> {
                                // Optionally you can check for subclasses of this error to give a more informative message
                                when (t) {
                                    is FTAlgorithmUnavailableException ->...
                                    is FTKeyNotFoundException ->...
                                    is FTKeystoreOperationException ->...
                                    is FTSignatureException -> ...
                                }
                            }
                            // API rejected the request. You can use the contained message variable or handle this based on subclass  
                            is FTApiException -> {
                                handleApiError(t)
                            }
                        }
                    }
                }
        }
}

/**
 * Generic function for handling FTApiException and subclasses, can be used for all SDK operations that throw FTApiException
 */
private fun handleApiError(t: FTApiException) {
  when(t) {
    is FTApiAdaptiveMigrationDisabledException -> ...
    is FTApiAdaptiveMigrationFailedException -> ...
    is FTApiAuthorizationFailedException -> ...
    is FTApiBadRequestException -> ...
    is FTApiContentNotModifiedException -> ...
    is FTApiDeviceArchivedException -> ...
    is FTApiInternalServerException -> ...
    is FTApiNoContentException -> ...
    is FTApiOperationForbiddenException -> ...
    is FTApiPinIncorrectException -> ...
    is FTApiPinMissingException -> ...
    is FTApiPinNotNeededException -> ...
    is FTApiPreconditionFailedException -> ...
    is FTApiRouteNotFoundException -> ...
    is FTApiGenericException -> ...
  }
}