Mobile SDK: iOS v3 (beta)

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

Prerequisites

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 iOS FuturaeKit SDK release history and changes here.

Migrating from SDK v2.x.x

If you are currently using SDK version v2.x.x, follow these steps to upgrade to the latest version, 3.x.x, which has been comprehensively rewritten in Swift. This update brings a host of improvements aimed at enhancing your app’s performance and development experience.

Step-by-Step Migration Guide

  1. Acquire SDK v3.x.x: Download the latest version of the SDK from our SDK repository.
  2. Refactor Your Application Code: As you integrate SDK v3.x.x, consider the following key changes:
  • Updated Method Names and Signatures: Method names and their signatures have been updated. Ensure that you align your existing code with these changes.
  • Strongly Typed Data Models: Where previously the SDK returned loosely defined or dictionary-type data, v3.x.x introduces strongly typed data models. This change enhances type safety and predictability of the data you work with.
  • Consolidated Methods: Similar methods have been streamlined for efficiency. For example, the approve and reject methods are now unified into a single method: replyAuth.
  • Swift Rewrite with Objective-C Compatibility: The SDK has been rewritten in Swift, offering the benefits of modern Swift features. However, it maintains backward compatibility with Objective-C, ensuring a smooth transition.
  • Introduction of AsyncTask and AsyncTaskResult: We have introduced two new classes, AsyncTask and AsyncTaskResult, designed to modernize handling asynchronous operations in Swift. These classes replace traditional callback patterns with a more contemporary and efficient approach.
  • Enhanced Error Handling: SDK version 3.x.x introduces a more robust and informative error handling system compared to the previous versions. Errors returned by the SDK can be cast to SDKBaseError or its subclasses. This structure provides a more detailed understanding of the error context and causes. SDK v3.x.x ensures a uniform and predictable structure for all errors. This consistency simplifies error handling in your application.
  1. Initialize the SDK with the selected LockConfigurationType. If the selected LockConfigurationType is the same that was utilized with SDK v2, the already enrolled accounts will remain valid. Then if you wish to use another LockConfigurationType, after launch you can switch lock configuration. Otherwise, to initialize the SDK with a different LockConfigurationType than the one previously used on SDK v2, the SDK needs to be reset first and only then initialized with the new LockConfigurationType.

Installation

We are going to assume that you are using Xcode for your development. You can install FuturaeKit into your project using:

SwiftPM

As of Xcode 12.0 (and Swift Tools 5.3) it is possible to integrate FuturaeKit directly within Xcode.

To add FuturaeKit follow the usual way of adding a package dependency:

File -> Swift Packages -> Add Package Dependency

In the window that pops up simply enter: https://github.com/Futurae-Technologies/ios-sdk-beta.git as the package repository URL.

The FuturaeKit framework should automatically be added to your project: you’re good to go!

To make sure that your application runs on a device, add the following “Run Script Phase” command to your App’s target build phases, to force deep sign the frameworks with your own signing identity:

find "${CODESIGNING_FOLDER_PATH}" -name '*.framework' -print0 | while read -d $'\0' framework
do
    codesign --force --deep --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements --timestamp=none "${framework}"
done

Carthage

Carthage is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods.

To install with Carthage, follow the instruction on Carthage. We support integration using Carthage binary frameworks. You can add FuturaeKit and its external dependencies by adding the following lines to your Cartfile

binary "https://raw.githubusercontent.com/Futurae-Technologies/ios-sdk/master/CarthageJson/FuturaeKit.json"
github "ReactiveX/RxSwift" ~> 6.6.0
github "stephencelis/SQLite.swift" ~> 0.14.1

Then run the command carthage update --platform iOS --use-xcframeworks. This command will fetch and build the specified dependencies. The --use-xcframeworks flag is important to ensure that Carthage builds XCFrameworks where applicable.

On your application targets' “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase”. Create a Run Script in which you specify your shell (e.g., /bin/sh), add the following contents to the script area below the shell:

/usr/local/bin/carthage copy-frameworks

Add the paths to the frameworks you want to use under “Input Files”, e.g.:

$(SRCROOT)/Carthage/Build/iOS/FuturaeKit.framework
$(SRCROOT)/Carthage/Build/iOS/RxSwift.framework
$(SRCROOT)/Carthage/Build/iOS/SQLite.framework

CocoaPods

It is assumed that your application has CocoaPods integrated. Otherwise please follow the official CocoaPods guide to integrate CocoaPods dependency manager to your app (How to install CocoaPods, How to use cocoapods).

The snippet below is a minimal Podfile to integrate FuturaeKit to your project. The notes are provided below.

platform :ios, '12.0' #1
source 'https://github.com/CocoaPods/Specs.git' #2

target 'FuturaeKitCocoapods' do
  use_frameworks! #3
  pod 'FuturaeKitBeta' #4
end

Notes:

  1. As per CocoaPods requirements, one may specify iOS minimal supported version. The Podfile needs to comply with a minimal version constraint of every library. The FuturaeKit supports iOS versions 12.0 and above. Reference: Cocoapods syntax guide.
  2. CocoaPods uses a CDN to deliver specs. It means that the source directive is not strictly necessary. However, there is no policy as to when and how often the CDN is updated. Because of that, the newest FuturaeKit package might not be available under CDN on the same day that the release is published, or even several days after. To get timely updates, please include the source directive. Otherwise the immediate availability of latest FuturaeKit package updates is not guaranteed.
  3. FuturaeKit is distributed as a compiled framework. Depending on your project needs, you may or not use the use_frameworks directive. This setting should not affect FuturaeKit’s linking to your project.
  4. pod directive instructs CocoaPods to add FuturaeKit to your project. It’s possible to freeze a specific FuturaeKit version if you prefer. Please refer to the official guide on how to specify a pod version.

Configuration and initialization

The NSFaceIDUsageDescription key is required for apps that uses Face ID APIs, that being the case this key needs to be declared in your Info.plist file, in order to describe the reasons for the app to use Face ID.

FuturaeKit SDK is a dynamic framework, so you should use the following import statement:

import FuturaeKit

Initialize the SDK

In the Project Navigator, open the source file of your application delegate and add the above import statement at the top of the file. Then, in the didFinishLaunching or didFinishLaunchingWithOptions method of your app delegate, create the SDK configuration instance FTRConfig, and provide it as argument to the launch method:

import FuturaeKit

// ...
let lockConfiguration = LockConfiguration(type: .biometricsOnly,
                                            unlockDuration: 60,
                                            invalidatedByBiometricsChange: true)
let config = FTRConfig(sdkId: "{FuturaeSdkId}",
                        sdkKey: "{FuturaeSdkKey}",
                        baseUrl: "https://api.futurae.com:443",
                        lockConfiguration: lockConfiguration)

do {
    try FTRClient.launch(config: config)
} catch let error as SDKBaseError {
    switch(error.errorAssociatedType){
        case .sdk(let error):
            break
        case .sdkLock(let error)
            break
        // other cases
    }
} catch {
    // handle generic Error
}

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 Futurae Support in order to enable it.

Once the SDK is initialized, the FTRClient instance may be accessed through the following getter:

FTRClient.shared

Build your app

Build and run your app. If it is successful, you should be able to see the SDK logs in the console.

Keychain configuration

Keychain sharing and access groups

By default the SDK stores its keychain items in the app’s default access group. This should be sufficient in most cases. Nevertheless, if your app is sharing access to keychain items with other apps then from a security point of view, the keychain items which belong to the Futurae SDK should not be shared, and thus they should be stored in an access group that is only accessible by the app that hosts the SDK. If the app’s default access group is shared with other apps, then another one should be used instead.

For this reason, the SDK provides the ability to specify the keychain access group to which the SDK-owned keychain items should be stored. The specified access group must not be shared with other apps.

import FuturaeKit

// ...
let lockConfiguration = LockConfiguration(type: .biometricsOnly,
                                            unlockDuration: 60,
                                            invalidatedByBiometricsChange: true)
let keychainConfig = FTRKeychainConfig(accessGroup: "{teamID}.com.example.appPrivateGroup")
let config = FTRConfig(sdkId: "{FuturaeSdkId}",
                        sdkKey: "{FuturaeSdkKey}",
                        baseUrl: "https://api.futurae.com:443",
                        keychain: keychainConfig,
                        lockConfiguration: lockConfiguration)

//launch SDK with configuration

When a keychain access group is specified, like in the example above, the SDK will automatically migrate all its keychain items to the specified access group, in the case that they are previously stored in a different access group. This ensures that if an already existing app, which was not originally configured to specify an access group when initializing the SDK), changes the behavior to specify an access group in a future release, then existing installations will have the Futurae SDK keys migrated to the specified access group after the relevant app update.

Note that if the SDK cannot store its items in the specified access group (for example, if the app doesn’t have the entitlement to do so) then it will throw an error.

Keychain items accessibility

By default the SDK stores its keychain items using the kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly accessibility value.

You can choose a different accessibility value, depending on the use case and requirements of your application, using one of the available FTRKeychainItemAccessibility values (which map directly to the respective iOS accessibility values), when initializing the SDK. For example:

import FuturaeKit

// ...
let lockConfiguration = LockConfiguration(type: .biometricsOnly,
                                            unlockDuration: 60,
                                            invalidatedByBiometricsChange: true)
let keychainConfig = FTRKeychainConfig(itemsAccessibility: .whenUnlockedThisDeviceOnly)
let config = FTRConfig(sdkId: "{FuturaeSdkId}",
                        sdkKey: "{FuturaeSdkKey}",
                        baseUrl: "https://api.futurae.com:443",
                        keychain: keychainConfig,
                        lockConfiguration: lockConfiguration)
//launch SDK with configuration

Refer to the content of the FTRKeychainConfig link for further information on the available options for FTRKeychainItemAccessibility.

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.

Features

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

Push notifications

To enable push notifications add the following call in the didFinishLaunching or didFinishLaunchingWithOptions method of your app delegate:

// push notifications
let center = UNUserNotificationCenter.current()
center.delegate = self
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
    //
}

UIApplication.shared.registerForRemoteNotifications()

To send the push notification token, add the following call in the didRegisterForRemoteNotificationsWithDeviceToken method of your app delegate:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    FTRClient.shared.registerPushToken(deviceToken) {
        // success
    } failure: { error in
        //
    }
}

To handle the Futurae push notifications, implement the FTRNotificationDelegate and add the handleNotification call in the respective method of your app delegate (didReceiveRemoteNotification:fetchCompletionHandler: or pushRegistry:didReceiveIncomingPushWithPayload:):

func application(
    _ application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable : Any],
    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
    FTRClient.shared.handleNotification(payload, delegate: self)
}

IMPORTANT:The handleNotification method requires the SDK to be unlocked in case that the notification payload includes extra_info. To check if the payload has extra info you can check the boolean value for the key has_extra in the payload dictionary: payload[@"has_extra"]

Encrypted Push Notifications

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. In case the session contains extra_info, the encrypted data can be retrieved as a string from the userInfo dictionary of the push notification under the key encrypted_info_enc. This string can be passed to the SDK protected function decryptExtraInfo which will decrypt it and return an array of FTRExtraInfo instances:

    // userInfo: [String: Any]
    let encryptedExtraInfo: String = userInfo["extra_info_enc"];
    let userId: String = userInfo["user_id"];
    let extraInfo = try FTRClient.shared.decryptExtraInfo(encrypted, userId: userId)

The SDK function decryptExtraInfo is a protected function so the SDK has to be unlocked before decrypting the extra information content.

In order to call the SDK method decryptExtraInfo within an app extension such as UNNotificationServiceExtension, the App Groups and Keychain Access Group needs to be configured as described in the section below.

Configuring App Group and Keychain Access Group

In order to call SDK methods from app extensions (such as UNNotificationServiceExtension) or to share the same SDK between different apps you need to utilize the App Groups and Keychain Access Group capabilities.

When initializing the SDK in the main app or the app extension, the app group and keychain app group identifier may be passed as following:

let lockConfiguration = LockConfiguration(type: .biometricsOnly,
                                            unlockDuration: 60,
                                            invalidatedByBiometricsChange: true)
let keychainConfig = FTRKeychainConfig(accessGroup: "{KeychainAccessGroupId}")
let config = FTRConfig(sdkId: "{FuturaeSdkId}",
                        sdkKey: "{FuturaeSdkKey}",
                        baseUrl: "https://api.futurae.com:443",
                        keychain: keychainConfig,
                        lockConfiguration: lockConfiguration,
                        appGroup: "{AppGroupId}")
//launch SDK with configuration

Migrating to using App Group and Keychain Access Group for the SDK

SDK instances created without setting the appGroup and keychain parameter in the launch configuration, will need to explicitly call the protected method updateSDKConfig in order to update the SDK configuration with the appGroup and Keychain details.

let keychainConfig = FTRKeychainConfig(accessGroup: "{KeychainAccessGroupId}")
FTRClient.shared.updateSDKConfig(appGroup: "{AppGroupId}", keychainConfig: keychainConfig) {
    // Upon success you can set a bool value in the user defaults and check for this value in subsequent launches of the SDK
    // and if set to true you can include the new values for app group and keychain parameter in the launch configuration.
    UserDefaults.standard.set(true, forKey: "app_groups_migrated")
} failure: { error in
    //
}

Check if SDK data exists for a specified configuration

In order to check if there is data present for a specific SDK configuration, the checkDataExists method can be called before launching the SDK. This method accepts as argument the App Group id, a keychain configuration object with the Keychain Access Group id and the SDK lockConfiguration object, and returns true if SDK is able to find data matching the provided configuration, otherwise it returns false.


//appGroup and keychainConfig can be nil to check if there is SDK data without App Group and Keychain Access Group configuration
let keychainConfig = FTRKeychainConfig(accessGroup: "{KeychainAccessGroupId}")
let lockConfiguration = LockConfiguration(type: .biometricsOnly,
                                            unlockDuration: 60,
                                            invalidatedByBiometricsChange: true)
let sdkDataExists = FTRClient.checkDataExists(forAppGroup: "{AppGroupId}",
                                                keychainConfig: keychainConfig,
                                                lockConfiguration: lockConfiguration)

When switching between configurations in the SDK, such as updating the configuration to use App Group, switching lock configuration types, or changing the hosting app keychain access group, the information about the recently used configuration might be lost on the app side in subsequent launches of the SDK. In that case, by calling the method checkDataExistsForAppGroup:keychainConfig:lockConfiguration: before launching, the app can determine which configuration has existing data and then proceed launching with that configuration if the method returns true.

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. It leverages the device lock mechanisms, such as biometrics (e.g. Touch ID or Face ID), passcode or an SDK PIN (also known as Custom App PIN) set by the user upon the first enrollment, to validate user presence when certain actions are carried out by the Futurae SDK, thus providing an extra layer of security to these operations.

The LockConfiguration init method 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.

type - This parameter sets the user presence verification lock configuration type between the following options:

  • LockConfigurationType.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.
  • LockConfigurationType.biometricsOnly - This lock configuration type allows the end user to only unlock the SDK through strong biometric authentication.
  • LockConfigurationType.biometricsOrPasscode - The SDK can be unlocked by authenticating either using the device biometrics or the passcode.
  • LockConfigurationType.sdkPinWithBiometricsOptional - 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.

unlockDuration - 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 throw an error. If the unlock duration is not set, then the SDK will assume a default value of 60 seconds.

invalidatedByBiometricsChange - If set to true, any change in the device biometrics settings 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:

  • LockConfigurationType.sdkPinWithBiometricsOptional - The app can either re-activate the 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, 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 method with biometrics can be used when the SDK lock configuration type is LockConfigurationType.biometricsOnly or LockConfigurationType.sdkPinWithBiometricsOptional.

When the SDK lock configuration type is LockConfigurationType.sdkPinWithBiometricsOptional, a server-side verification of the SDK PIN takes place, therefore network connectivity is required.

// Using `UnlockBiometrics` class
FTRClient.shared.unlock(UnlockBiometrics(promptReason: "Unlock SDK"), success: {
    // unlock verification success
}, failure: { error in
    // user verification failed
})

// Alternatively, using convenience method `with` of class `UnlockParameters`
FTRClient.shared.unlock(.with(biometricsPrompt: "Unlock SDK"), success: {
    // unlock verification success
}, failure: { error in
    // user verification failed
})

Verify user presence with biometrics or device passcode

If the SDK lock configuration type is set as LockConfigurationType.biometricsOrPasscode, 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 passcode.

The method unlock 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 passcode.

// Using `UnlockBiometricsOrPasscode` class
FTRClient.shared.unlock(UnlockBiometricsOrPasscode(promptReason: "Unlock SDK"), success: {
    // unlock verification success
}, failure: { error in
    // user verification failed
})

// Alternatively, using convenience method `with` of class `UnlockParameters`
FTRClient.shared.unlock(.with(biometricsOrPasscodePrompt: "Unlock SDK"), success: {
    // unlock verification success
}, failure: { error in
    // user 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 unlock by providing the 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 LockConfigurationType.sdkPinWithBiometricsOptional 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.

// The app must implement a mechanism to ask the user to insert the sdkPIN

// Using `UnlockSDKPin` class
FTRClient.shared.unlock(UnlockSDKPin(SDKPin: sdkPin), success: {
    // unlock verification success
}, failure: { error in
    // user verification failed
})

// Alternatively, using convenience method `with` of class `UnlockParameters`
FTRClient.shared.unlock(.with(SDKPin: sdkPIN), success: {
    // unlock verification success
}, failure: { error in
    // user 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.

Request operations related to SDK PIN may return one of the following cases of SDKApiErrorCode type, which can be found as sdkCode property in the custom error SDKApiError class:

  • SDKApiErrorCode.pinNotNeeded: When the SDK PIN is provided but not required.
  • SDKApiErrorCode.missingPin: Indicates that the SDK PIN is missing or empty.
  • SDKApiErrorCode.incorrectPin: If the user enters an incorrect SDK PIN. SDKApiError error class has a pinAttemptsLeft property to check in this case how many attempts are left to enter the correct SDK PIN.
  • SDKApiErrorCode.incorrectPinArchivedDevice: 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 lock function.

try FTRClient.shared.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 (FTRClient.shared.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 activeUnlockMethods returns a list of UnlockMethodType enum values that the SDK can currently be unlocked with:

let unlockMethods = FTRClient.shared.activeUnlockMethods

//UnlockMethodType values
@objc public enum UnlockMethodType: Int {
    case biometrics = 1
    case biometricsOrPasscode = 2
    case sdkPin = 3
    case none = 4
}

In Objective-C you can retrieve the number values for the enum types instead using method activeUnlockMethodsValues:

NSArray<NSNumber *> unlockMethods = [FTRClient.shared activeUnlockMethodsValues];

Switch SDK configuration

The SDK provides a way to switch to a different SDK configuration post initialization via the following methods: switchToLockConfiguration

The following classes can be used as a parameter for the switch configuration method:

SwitchToLockBiometrics
SwitchToLockBiometricsOrPasscode
SwitchToLockNone
SwitchToLockSDKPin

Or using the convenience methods of the SwitchLockParameters class:

class func with(SDKPin: String, newLockConfiguration: LockConfiguration) -> SwitchToLockSDKPin
class func with(biometricsOrPasscodePrompt: String, newLockConfiguration: LockConfiguration) -> SwitchToLockBiometricsOrPasscode
class func with(biometricsPrompt: String, newLockConfiguration: LockConfiguration) -> SwitchToLockBiometrics
class func with(newLockConfiguration: LockConfiguration) -> SwitchToLockNone

QR codes

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

  • Enroll a device for a user (online)
  • Authenticate a user or transaction (online)
  • Authenticate a user or transaction (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:

let type = FTRClient.qrCodeType(fromQRCode: "qr code string...")
switch type {
    case .enrollment:
        // handle Enrollment
    case .onlineAuth:
        // handle Online QR code authentication
    case .offlineAuth:
        // handle Offline QR code authentication
    case .usernameless:
        // handle usernameless QR code
    case .invalid:
        // handle invalid QR code
}

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 activation code

To enroll this SDK as a device for a Futurae User, call the enroll method using a valid activation code, for example obtained by scanning an enrollment QR Code:

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

// Using `EnrollActivationCode` class
FTRClient.shared.enroll(EnrollActivationCode(code: "activation code ..")) {
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class`EnrollParameters`
FTRClient.shared.enroll(.with(activationCode: "activation code ..")) {
    // success
} failure: { error in
    //
}

Enroll with activation shortcode

To enroll with an activation shortcode, call the the protected method enroll:

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

// Using `EnrollShortCode` class
FTRClient.shared.enroll(EnrollShortCode(code: "activation short code ..")) {
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class`EnrollParameters`
FTRClient.shared.enroll(.with(shortCode: "activation short code ..")) {
    // success
} failure: { error in
    //
}

Enroll and setup the SDK PIN

When the SDK lock configuration type is LockConfigurationType.sdkPinWithBiometricsOptional, the user must set the SDK PIN upon the first enrollment, therefore the activation of the first account is perfomed by calling the SDK methods enroll instead, and providing as argument the PIN inserted by the user.

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).

Enroll with activation code and SDK Pin:

// Using `EnrollActivationCodeSDKPin` class
FTRClient.shared.enroll(EnrollActivationCodeSDKPin(code: "activation code ..", SDKPin: "sdkPin")) {
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class`EnrollParameters`
FTRClient.shared.enroll(.with(activationCode: "activation code ..", SDKPin: "sdkPin")) {
    // success
} failure: { error in
    //
}

Enroll with short code and SDK Pin:

// Using `EnrollShortCodeSDKPin` class
FTRClient.shared.enroll(EnrollShortCodeSDKPin(code: "activation short code ..", SDKPin: sdkPin)) {
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class`EnrollParameters`
FTRClient.shared.enroll(.with(shortCode: "activation short code ..", SDKPin: sdkPin)) {
    // success
} failure: { error in
    //
}

Logout (Unenroll)

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

// first, make sure the SDK is unlocked, then proceed
FTRClient.shared.logoutAccount(account) {
    //success
} failure: { error in
    //
}

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.

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 the 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.biometricsOrPasscode, LockConfigurationType.sdkPinWithBiometricsOptional or LockConfigurationType.biometricsOnly (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.sdkPinWithBiometricsOptional, then biometrics verification must be enabled at the SDK level. To do so, the function activateBiometrics must be called. 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 invalidatedByBiometricsChange 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 method activateBiometrics, after verifying the user presence to unlock the SDK.

The activateBiometrics method is only available for the LockConfigurationType.sdkPinWithBiometricsOptional lock configuration type. If called right after enrolling the first account and setting up the SDK PIN with the method enroll, 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
try FTRClient.shared.activateBiometrics()

Deactivate SDK biometrics verification

If the user wishes to deactivate biometrics verification, the app may use the protected deactivateBiometrics method 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 to unlock the SDK.

//first, make sure the SDK is unlocked with SDK PIN verification, then proceed
try FTRClient.shared.deactivateBiometrics()

Check if the SDK biometrics are valid

Specifically when the SDK lock configuration type is LockConfigurationType.sdkPinWithBiometricsOptional and the invalidatedByBiometricsChange settings is configured as 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 LockConfigurationTypeBiometrics or LockConfigurationType.biometricsOrPasscode.

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

FTRClient.shared.haveBiometricsChanged

Change SDK PIN

When the SDK lock configuration type is LockConfigurationType.sdkPinWithBiometricsOptional, 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
FTRClient.shared.changeSDKPin(newSDKPin:pin) {
    // success
} failure: { error in
    //
}

Account status

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

let accounts = try FTRClient.shared.getAccounts()

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

FTRClient.shared.getAccountsStatus(accounts) { status in
    // success
} failure: { error in
    // 
}

Account History

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

FTRClient.shared.getAccountHistory(account) { history in
    // success
} failure: { error in
    //
}

Authenticate user

To authenticate (or reject) a user session, depending on the authentication factor, you can use the following methods:

One-Touch

With One-Touch (aka Approve), the server will send a push notification to the app, where the user should approve or reject the authentication session.

Get the user ID and session ID from the push notification handler. Once the outcome of the authentication has been decided (e.g., approved/rejected by the user), it should be sent to the server for the authentication session to complete, using one of the methods described in the subsections below.

Approve Authentication

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

The following example demonstrates how to approve an authentication session when the SDK is unlocked:

// Approve using `ApproveAuthPush` class
FTRClient.shared.replyAuth(ApproveAuthPush(userId: userId, sessionId: sessionId, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `approvePush` of class `AuthParameters`
FTRClient.shared.replyAuth(.approvePush(sessionId, userId: userId, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}
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 replyAuth method:

// Reject using `RejectAuthPush` class
FTRClient.shared.replyAuth(RejectAuthPush(userId: userId, sessionId: sessionId, isFraud: false, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `rejectPush` of class `AuthParameters`
FTRClient.shared.replyAuth(.rejectPush(sessionId, isFraud: false, userId: userId, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}
Approve with Multi-Numbered Challenge

If Multi-Numbered Challenge is enabled for your Futurae Service, push notifications sent to the app will contain an array of numbers in the multiNumberedChallenge property which can be found within the authenticationInfo object provided by the method approveAuthenticationReceived(_:) of the FTRNotificationDelegate protocol.

// for example in AppDelegate
func approveAuthenticationReceived(_ authenticationInfo: FTRNotificationAuth){
    let numbersChallenge = authenticationInfo.multiNumberedChallenge
}

You can also retrieve the multiNumberedChallenge values from the session info (refer to authentication session information):

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 SDK’s replyAuth method.

// Approve using `ApproveAuthMultiNumber` class
FTRClient.shared.replyAuth(ApproveAuthMultiNumber(userId: userId, sessionId: sessionId, multiNumberChoice: multiNumberChoice, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `approvePushMultiNumber` of class `AuthParameters`
FTRClient.shared.replyAuth.approvePushMultiNumber(multiNumberChoice, sessionId: sessionId, userId: userId, extraInfo: extraInfo) {
    // success
} failure: { error in
    //
}

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.

Online QR code

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

In the following example the user is verified by validating the SDK PIN, then the content of the online QR code scanned by the user, is passed to the protected method replyAuth:

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

// Approve using `ApproveAuthQRCode` class
FTRClient.shared.replyAuth(ApproveAuthQRCode(qrCode: qrCodeScanResult, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `approveQRCode` of class `AuthParameters`
FTRClient.shared.replyAuth(.approveQRCode(qrCodeScanResult, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}

In case the backend API Authenticate with QR code (or Authenticate Transaction with QR code) is invoked with supplying the extra_info input parameter (for example, when contextual information for logins, or transaction details for transaction authentication), which includes information that must be displayed to the user before they can approve/reject the request, then you must retrieve this information from the Futurae backend and include it in the authentication response. See section authentication session information for more information.

QR Easy Scan

Futurae SDK can inform the host app when an online QR code authentication is requested for one of the enrolled accounts. The SDK uses FTRNotificationDelegate to relay the message to the host app as described in the Push notifications section.

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. The SDK calls FTRNotificationDelegate’s method qrCodeScanRequested(_:_:_:) and the host app handles it accordingly.

Notes:

  • It is assumed that the SDK successfully receives push notifications. To ensure that, the host app needs to implement push notification handling as described in the Push notifications section.
  • SDK has to be initialized prior to the handleNotification call.
  • The push notification that the SDK receives is silent by default, and it is up to the host app to act on the QR push requested signal.
  • To complete the authentication, provide the scanned QR code data to the SDK replyAuth function as describe 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 LockConfigurationType.sdkPinWithBiometricsOptional 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 is a protected function that requires prior unlocking of the SDK. However, for the case that the app is offline the SDK offers the following equivalent function that are not protected but require either passing the SDK PIN as argument or if biometrics are activated then the getOfflineQRVerificationCode method shall be used.

The method getOfflineQRVerificationCode generates the verification code by passing the SDK PIN as argument:

// Get verification code using `OfflineQRCodeSDKPin` class
FTRClient.shared.getOfflineQRVerificationCode(OfflineQRCodeSDKPin(qrCode: "qrCode", SDKPin: "sdkPin")) { verificationCode in
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class`OfflineQRCodeParameters`
FTRClient.shared.getOfflineQRVerificationCode(.with(qrCode: "qrCode", SDKPin: "sdkPin")) { verificationCode in
    // success
} failure: { error in
    //
}

To generate the verification code when the biometrics verification is activated, call the getOfflineQRVerificationCode method, which will initiate the user biometrics verification prompt, and then compute the code based on the biometrics result:

// Get verification code using `OfflineQRCodeSDKPinWithBiometrics` class
FTRClient.shared.getOfflineQRVerificationCode(OfflineQRCodeSDKPinWithBiometrics(qrCode: "qrCode", promptReason: "promptReason")) { verificationCode in
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class`OfflineQRCodeParameters`
FTRClient.shared.getOfflineQRVerificationCode(.with(qrCode: "qrCode", promptReason: "promptReason")) { verificationCode in
    // success
} failure: { error in
    //
}

Finally, the verification code can be computed by passing only the QR code data to the getOfflineQRVerificationCode method. Nevertheless, user presence has to be verified before that:

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

// Get verification code using `OfflineQRCodeDefault` class
FTRClient.shared.getOfflineQRVerificationCode(OfflineQRCodeDefault(qrCode: "qrCode")) { verificationCode in
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class`OfflineQRCodeParameters`
FTRClient.shared.getOfflineQRVerificationCode(.with(qrCode: "qrCode")) { verificationCode in
    // success
} failure: { error in
    //
}

IMPORTANT: In case the backend API Authenticate with Offline QR code (or Authenticate Transaction with Offline QR code) is invoked with supplying the extra_info input parameter (for example, when contextual information for logins, or transaction details for transaction authentication), which includes information that must be displayed to the user before they can approve/reject the request, then you can retrieve this information from the QR code itself, as shown below:

NSArray<FTRExtraInfo *> *extraInformation = [FTRClient.sharedClient extraInfoFromOfflineQRCode:QRCodeString];

Usernameless QR Code

To approve 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 with an enrolled user’s id using the protected methods replyAuth:

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

// Approve using `ApproveAuthUsernameless` class
FTRClient.shared.replyAuth(ApproveAuthUsernameless(qrCode: "qr code scan result..", userId: userId, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `approveUsernamelessQRCode` of class `AuthParameters`
FTRClient.shared.replyAuth(.approveUsernamelessQRCode("qr code scan result", userId: userId, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}

To reject a usernameless QR code session use the protected method replyAuth:

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

// Reject using `RejectAuthUsernameless` class
FTRClient.shared.replyAuth(RejectAuthUsernameless(qrCode: "qr code scan result", userId: userId, isFraud: false, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `rejectUsernamelessQRCode` of class `AuthParameters`
FTRClient.shared.replyAuth(.rejectUsernamelessQRCode("qr code scan result", isFraud: false, userId: userId, extraInfo: extraInfo)) {
    // success
} failure: { error in
    //
}

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:

If you have a session ID:

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

// Using `SessionId` class
FTRClient.shared.getSessionInfo(SessionId("session id", userId: userId)) { session in
    // success
} failure: { error in
    // failure
}

// Alternatively, using convenience method `with` of class `SessionParameters`
FTRClient.shared.getSessionInfo(.with(id: "session id", userId: userId)) { session in
    // success
} failure: { error in
    // failure
}

If you have a session token: :

// Using `SessionToken` class
FTRClient.shared.getSessionInfo(SessionToken("session token", userId: userId)) { session in
    // success
} failure: { error in
    // failure
}

// Alternatively, using convenience method `with` of class `SessionParameters`
FTRClient.shared.getSessionInfo(.with(token: "session token", userId: userId)) { session in
    // success
} failure: { error in
    // failure
}

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 of the FTRUtils class to obtain these from either a URI or a QR Code:

static func sessionToken(fromQRCode qrCode: String) -> String?
static func userId(fromQRCode qrCode: String)
static func sessionToken(fromUri uri: String) -> String?
static func userId(fromUri uri: String) -> String?

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 getSessionInfo method, 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 for the specified user identifier:

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

// Using `TOTPDefault` class
FTRClient.shared.getTOTP(TOTPDefault(userId: userID)) { result in
    // success

    // The TOTP that the user should use to authenticate
    let totp = result.totp;

    // The remaining seconds of validity of this TOTP
    let remainingSecs = result.remainingSecs;
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class `TOTPParameters`
FTRClient.shared.getTOTP(.with(userId: userID)) { result in
    // success
} failure: { error in
    //
}

As seen in this example, the getTOTP method returns an object that contains the TOTP itself, but also the remaining seconds that this TOTP will be still valid for. After this time, a new TOTP must be obtained by the app.

When the SDK lock configuration type is LockConfigurationType.sdkPinWithBiometricsOptional, 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:

// Using `TOTPSDKPin` class
FTRClient.shared.getTOTP(TOTPSDKPin(userId: userId, SDKPin: "sdkPin")) { result in
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class `TOTPParameters`
FTRClient.shared.getTOTP(.with(userId: userID, SDKPin: "sdkPin")) { result in
    // success
} failure: { error in
    //
}

When the SDK is configured with LockConfigurationType.sdkPinWithBiometricsOptional, if biometrics verification is activated, the TOTP may be generated using the getTOTP method:

// Using `TOTPSDKPinWithBiometrics` class
FTRClient.shared.getTOTP(TOTPSDKPinWithBiometrics(userId: userId, promptReason: "biometrics prompt reason")) { result in
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class `TOTPParameters`
FTRClient.shared.getTOTP(.with(userId: userId, promptReason: "biometrics prompt reason")) { result in
    // success
} failure: { error in
    //
}

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 getSynchronousAuthToken method:

 try FTRClient.shared.getSynchronousAuthToken(userId: 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. To handle the Futurae URI scheme calls, implement the FTROpenURLDelegate and add the openURL call in the openURL:sourceApplication:annotation: method of your app delegate:

//First, make sure the SDK is unlocked, then proceed
FTRClient.shared.openURL(url, options: options, delegate: self)

IMPORTANT: In case you perform authentication using 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 iOS SDK installation to:

  1. the same or a different device when restoring the device via an iCloud or encrypted iTunes backup, or
  2. the same device after erasing and reinstalling the SDK-enabled mobile app.

This 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 number of accounts which can be migrated. In order to do so, invoke the method getMigratableAccounts:

FTRClient.shared.getMigratableAccounts { result in
    // success
    if (result.pinProtected) {
        //Proceed with account migration with SDKPin
    } else {
        //Proceed with default account migration
    }
} failure: { error in
    //
}

To execute account migration invoke the migrateAccounts method, depending on the result of the check operation above and your active SDK configuration’s LockConfigurationType. The migrated accounts (i.e., Futurae user ids) will be returned, if successful.

FTRClient.shared.migrateAccounts { accounts in
    // success
} failure: { error in
    //
}

The method migrateAccounts with SDK PIN should be called in either of the below scenarios:

  • If the accounts to be recovered were originally enrolled on an SDK configured with SDK PIN (or later switched to an SDK PIN configuration), then the user must be asked to insert the SDK PIN configured on the previous SDK in order to confirm the account migration process, and the inserted SDK PIN is provided as argument to the migrateAccounts method.
  • If the accounts to be recovered were not protected by an SDK PIN and the current lock configuration type is LockConfigurationType.sdkPinWithBiometricsOptional the provided SDK PIN to the migrateAccounts method will be used as the new SDK PIN for unlocking the SDK.
// Using `MigrationSDKPin` class
FTRClient.shared.migrateAccounts(MigrationSDKPin(SDKPin: sdkPin)) { accounts in
    // success
} failure: { error in
    //
}

// Alternatively, using convenience method `with` of class `MigrationParameters`
FTRClient.shared.migrateAccounts(.with(SDKPin: sdkPin)) { accounts in
    // success
} failure: { error in
    //
}

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

  • If restoring a device from a backup, the migration data of the SDK in previous app installation must have been included in the backup of the device from which the current device is restored. Additionally, they iCloud Keychain must have been enabled in the previous device and must be enabled in the new device, as well.
  • The SDK in the new app installation must have no other accounts currently or previously enrolled.

SDKMigrationErrorCode is a dedicated enum to distinguish errors that might occur during checking or executing account migration, which is found as sdkCode property in the custom error SDKMigrationError class :

enum SDKMigrationErrorCode: Int {
    case unknown = 1
    case migrationInfoMissing = 2
    case accountsExistError = 3
    case accountPreviouslyEnrolledError = 4
    case pinRequired = 5
    case noDeviceUDID = 6
    case noMigrationToken = 7
}

Trusted Session Binding

Trusted Session Binding is a security enhancement that strengthens SDK operations through additional authentication by utilizing binding tokens, reducing the risk of unauthorized access and enhancing security without compromising user experience. By requiring users to complete additional authentication steps before enrollment or account recovery, a secure and direct linkage between the user’s actions and their session is ensured, effectively mitigating the risk of unauthorized access.

Enrollment

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 bindingToken parameter to the enroll method. For example:
FTRClient.shared.enroll(.with(activationCode: "activation code ..", bindingToken: "token ..")) {
    // success
} failure: { error in
    //
}

Account Recovery

When Trusted Session Binding is enabled for account recovery, 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 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 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 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 the bindingToken parameter to the migrateAccounts method. For example:
FTRClient.shared.migrateAccounts(.with(SDKPin: sdkPin, bindingToken: "token ..")) {
    // success
} failure: { error in
    //
}

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

There is a way to ask for permissions on your own (to avoid displaying all at once, one after another, and in order to sufficiently explain to the user why these permissions are needed and for what purpose the data is used), however you need to remember about postponing the enabling of the adaptive mechanism when doing so, since first you need to ask for all required permissions (code example below) and only then enable adaptive.

To inform the SDK which adaptive permissions are currently granted you need to provide a delegate which conforms to the FTRAdaptiveSDKDelegate delegate. You set this delegate when you enable the adaptive mechanism.

lazy var locationManager = CLLocationManager()
lazy var bluetoothManager = CBCentralManager()
lazy var serviceBrowser = NetServiceBrowser()

func requestForLocationPermission() {
    locationManager.requestWhenInUseAuthorization()
}

func requestForBluetoothPermission() {
    _ = bluetoothManager
}

func requestForNetworkPermission() {
    serviceBrowser.searchForServices(ofType: "_http._tcp.", inDomain: "") // or any other operation that requires network usages
}

To be able to use the adaptive mechanism in the Futurae SDK you have to configure the following first (important note: An exception will be thrown if these are not set up.)

1. Set up Bluetooth

For iOS 13+, Add Privacy - Bluetooth Always Usage Description key to your Info.plist.

<key>NSBluetoothAlwaysUsageDescription</key>
<string>{{App}} requires bluetooth access to scan for nearby devices.</string>

For iOS 6.0–13.0, Add Privacy - Bluetooth Peripheral Usage Description key to your Info.plist.

<key>NSBluetoothPeripheralUsageDescription</key>
<string>{{App}} requires bluetooth access to scan for nearby devices.</string>
2. Set up Local Network
2.1 Add WiFi capability

In your Xcode project, select the Capabilities tab and choose Access WiFi Information from the list.

2.2 Edit Info.plist

For iOS 14.0+, Add Privacy - Local Network Usage Description key to your Info.plist.

<key>NSLocalNetworkUsageDescription</key>
<string>{{App}} requires network access to scan your local network for devices and services</string>

Add Bonjour services key to your Info.plist with a list of services:

<key>NSBonjourServices</key>
<array>
  <string>_smb._tcp.</string>
  <string>_privet._tcp.</string>
  <string>_device-info._tcp.</string>
  <string>_sftp-ssh._tcp.</string>
  <string>_airplay._tcp.</string>
  <string>_scanner._tcp.</string>
  <string>_mediaremotetv._tcp.</string>
  <string>_rdlink._tcp.</string>
  <string>_rfb._tcp.</string>
  <string>_uscan._tcp.</string>
  <string>_companion-link._tcp.</string>
  <string>_apple-mobdev2._tcp.</string>
  <string>_b._dns-sd._udp.</string>
  <string>_afpovertcp._tcp.</string>
  <string>_nfs._tcp.</string>
  <string>_webdav._tcp.</string>
  <string>_ftp._tcp.</string>
  <string>_ssh._tcp.</string>
  <string>_eppc._tcp.</string>
  <string>_http._tcp.</string>
  <string>_telnet._tcp.</string>
  <string>_printer._tcp.</string>
  <string>_ipp._tcp.</string>
    <string>_pdl-datastream._tcp.</string>
  <string>_riousbprint._tcp.</string>
  <string>_daap._tcp.</string>
  <string>_dpap._tcp.</string>
  <string>_ichat._tcp.</string>
  <string>_presence._tcp.</string>
  <string>_ica-networking._tcp.</string>
  <string>_airport._tcp.</string>
  <string>_xserveraid._tcp.</string>
  <string>_distcc._tcp.</string>
  <string>_apple-sasl._tcp.</string>
  <string>_workstation._tcp.</string>
  <string>_servermgr._tcp.</string>
  <string>_raop._tcp.</string>
  <string>_xcs2p._tcp.</string>
</array>
3. Set up Location

For iOS 11.0+, Add Privacy - Location When In Use Usage Description key to your Info.plist.

<key>NSLocationWhenInUseUsageDescription</key>
<string>{{App}} requires access to your location</string>

Usage

In order to enable the adaptive mechanism you need to import the Adaptive SDK framework to your app. You can find the Adaptive SDK repository here.

After launching the SDK (see Initialize the SDK section), you can enable adaptive via the following:

FTRClient.shared.enableAdaptive(delegate: self)

Here delegate is an object that conforms to the FTRAdaptiveSDKDelegate protocol.

You can disable adaptive via the following:

FTRClient.shared.disableAdaptive()

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. E.g. 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.

Error handling

If Adaptive is enabled calling migrateAccounts may also return one of the following cases of SDKApiErrorCode type, which can be found as sdkCode property in the custom error SDKApiError class:

  • SDKApiErrorCode.operationForbidden when the accounts migration feature is disabled in the backend
  • SDKApiErrorCode.adaptiveMigrationFailed when the adaptive mechanism does not allow a migration to be completed successfully (only if the adaptive mechanism is enabled)

Public Key Pinning

The Futurae mobile SDK employs a public key pinning (SSL pinning) mechanism to protect secure traffic from the MITM attacks.

The SDK’s public key pinning mechanism is always enabled and no further configurations are required on the app side.

Device jailbreak detection

The SDK offers a mechanism to detect if the device on which the app is installed, is jailbroken. To run the test, call jailbreakStatus method of the FTRClient:

let status: JailbreakStatus = FTRClient.sharedClient.jailbreakStatus;

The returned object has a flag status.jailbroken that reports whether a device is jailbroken (true) or not (false). The property status.message provides a description concerning the jailbroken mechanism detected in the device, if any.

status.jailbroken // Boolean
status.message // String

Integrity verdict

The SDK offers a mechanism to validate that the application is installed from a valid channel of acquisition, i.e. Apple’s App Store. To run the integrity check, call the appAttestation method of the FTRClient.

FTRClient.shared.appAttestation(appId:"{appId}", production: true/false) {
    // success
} failure: { error in
    //
}

If error is not null, then the app integrity check has failed, otherwise the app integrity check has successfully passed.

For more information about Apple’s App Attest service visit the developer guide.

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 and SDK needs to be launched again.

FTRClient.shared.reset()

// If SDK was previously launched with `appGroup` parameter:
FTRClient.shared.reset(appGroup: "${appGroupId}")

The SDK may be reset in the following situations:

SDK client delegates

The SDK provides two primary delegate protocols that host applications can implement to receive notifications about various events, such as URL openings and push notification actions. These delegates are crucial for handling authentication, activation, QR code scanning, and user unenrollment actions within your application.

FTROpenURLDelegate

The FTROpenURLDelegate protocol informs the delegate about URL openings related to authentication and activation.

Methods:

FTRNotificationDelegate

The FTRNotificationDelegate protocol allows the delegate to be informed about events received via push notifications.

Methods:

SDK Error handling

The Error instances returned by the SDK (for example in methods that throw errors or error callbacks) may be cast to a custom SDKBaseError error class or to one of the more specific error subclasses of SDKBaseError, which may provide more detailed context about the error, such as the following ones:

Example of handling error in a do-try-catch statement:

do {
    let token = try FTRClient.shared.getSynchronousAuthToken(userId: "userid")
} catch let baseError as SDKBaseError {
    // handle generic sdk error
    print("Error message: \(baseError.message)")
} catch let sdkError as SDKError {
    // handle specific sdk error
    if sdkError.sdkCode == .invalidQRCode {
        print("Invalid QR Code, please scan another code")
    }
} catch let apiError as SDKApiError {
    // handle api error
    if apiError.sdkCode == .incorrectPin, let pinAttemptsLeft = apiError.pinAttemptsLeft {
        print("Attempts left to unlock with SDK PIN: \(pinAttemptsLeft)")
    }
} catch {
    // generic error handling
    print("Unknown error: \(error.localizedDescription)")
}

Instead of using multiple catch statements or to avoid manually casting the error into specfic subclasses of SDKBaseError class, you can switch through the errorAssociatedType property of the SDKBaseError class to match with the specific error subclass:

do {
    let token = try FTRClient.shared.getSynchronousAuthToken(userId: "userid")
} catch let baseError as SDKBaseError {
    switch baseError.errorAssociatedType {
    case .sdk(let error):
        // handle specific sdk error
        if error.sdkCode == .invalidQRCode {
            print("Invalid QR Code, please scan another code")
        }
    case .api(let error):
        // handle api error
        if error.sdkCode == .incorrectPin, let pinAttemptsLeft = error.pinAttemptsLeft {
            print("Attempts left to unlock with SDK PIN: \(pinAttemptsLeft)")
        }
    default:
        break
    }
} catch {
    // generic error handling
    print("Unknown error: \(error.localizedDescription)")
}

Asynchronous operations with AsyncTask and AsyncTaskResult

The AsyncTask and AsyncTaskResult classes are exposed with the purpose of providing a modern approach to handling asynchronous operations in Swift. All asynchronous methods of the SDK come in two variants now, meaning that they share the same method name but have different signatures:

  • Methods that accept a success and failure callback: these methods are compatible with Objective-C and Swift code.
  • Methods that return an AsyncTask or AsyncTaskResult: these methods are only compatible with Swift code. AsyncTask is a wrapper returned for operations that do not return a value (or return Void) whereas AsyncTaskResult class is returned for operations that provide a value as a result.

The AsyncTask/AsyncTaskResult operations can be executed in the following ways:

Execute with Result type callback

The Result enum in Swift provides a success and failure case:

FTRClient.shared.getMigratableAccounts().execute { result in
    switch result {
    case .success(let data):
        print("Accounts to migrate: \(data.numberOfAccountsToMigrate)")
    case .failure(let error):
        print(error)
    }
}

Swift async/await

With Swift 5.5 and later, AsyncTask and AsyncTaskResult can be adapted to work with the async/await syntax. This allows for a more streamlined syntax and better error handling, aligning with modern Swift concurrency patterns.

do {
    let data = try await FTRClient.shared.getMigratableAccounts().execute()
    print("Accounts to migrate: \(data.numberOfAccountsToMigrate)")
} catch {
    print(error)
}

Integration with Combine Publishers

For developers utilizing Apple’s Combine framework, AsyncTask and AsyncTaskResult also provides a way to return Publishers.

import Combine
        
var cancellable = Set<AnyCancellable>()

FTRClient.shared.getMigratableAccounts()
    .publisher
    .sink { completion in
        switch completion {
        case .finished:
            print("operation finished")
        case .failure(let error):
            print(error)
        }
    } receiveValue: { data in
        print("Accounts to migrate: \(data.numberOfAccountsToMigrate)")
    }.store(in: &cancellable)

Integration with RxSwift

The AsyncTask and AsyncTaskResult classes can be integrated with RxSwift, a library for reactive programming in Swift. This allows the tasks to be represented as Observables. Due to support for iOS 12 the SDK does not internally use Swift features like async/await or Combine features, instead it makes use of RxSwift internally.

Example with RxSwift:

import RxSwift

let bag = DisposeBag()

FTRClient.shared.getMigratableAccounts()
    .rx
    .subscribe { data in
        print("Accounts to migrate: \(data.numberOfAccountsToMigrate)")
    } onFailure: { error in
        print(error)
    }
    .disposed(by: bag)

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 lock configuration type (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 unlock functions
  • unlock
  • enroll (with EnrollActivationCodeSDKPin, EnrollShortCodeSDKPin)