Mobile SDK: iOS
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
- Acquire SDK v3.x.x: Download the latest version of the SDK from our SDK repository.
- 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
andreject
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
andAsyncTaskResult
: We have introduced two new classes,AsyncTask
andAsyncTaskResult
, 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.
- Initialize the SDK with the selected
LockConfigurationType
. If the selectedLockConfigurationType
is the same that was utilized with SDK v2, the already enrolled accounts will remain valid. Then if you wish to use anotherLockConfigurationType
, after launch you can switch lock configuration. Otherwise, to initialize the SDK with a differentLockConfigurationType
than the one previously used on SDK v2, the SDK needs to be reset first and only then initialized with the newLockConfigurationType
.
Installation
We are going to assume that you are using Xcode for your development. You can install FuturaeKit into your project using:
SwiftPM
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.git
as the package repository URL.
The FuturaeKit framework should automatically be added to your project: you’re good to go!
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"
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.
Drag and drop FuturaeKit.xcframework
file from the Carthage/Build folder into the “Frameworks and Libraries” and “Embedded Content” section of the General settings of your app’s Xcode project. Make sure these frameworks are embedded as “Embed & Sign”.
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 'FuturaeKit' #4
end
Notes:
- 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.
- 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. - 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. 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:
- Mobile SDK: iOS v3
- Prerequisites
- Release information
- Migrating from SDK v2.x.x
- Installation
- Configuration and initialization
- Keychain configuration
- Demo app
- Features
- Push notifications
- Configuring App Group and Keychain Access Group
- User presence verification
- QR codes
- Enroll
- Logout (Unenroll)
- SDK biometrics configuration
- Change SDK PIN
- Account status
- Account History
- Authenticate user
- Universal Links
- Automatic Account Recovery
- Trusted Session Binding
- Adaptive
- Public Key Pinning
- Device jailbreak detection
- Integrity verdict
- Reset SDK data
- SDK client delegates
- SDK Error handling
- Asynchronous operations with AsyncTask and AsyncTaskResult
- SDK protected functions
- SDK unlock functions
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 Configuring App Group and Keychain Access Group.
Custom in-app messaging
The Custom In-App Messaging feature enables apps using the Futurae SDK to receive notifications triggered via the Futurae Auth API, containing a custom payload defined by the app’s backend.
To handle the Futurae push notifications in the main app, implement the FTRNotificationDelegate
and add the handleNotification
call in the respective method of your app delegate.
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
FTRClient.shared.handleNotification(payload, delegate: self)
}
The SDK will recognize the custom notification type and load the message data, which your app will then receive through the FTRNotificationDelegate method notificationDataReceived
.
extension AppDelegate: FTRNotificationDelegate {
...
func notificationDataReceived(_ notificationData: FTRNotificationData) {
// Handle the notification data
}
}
To handle notifications in a UNNotificationServiceExtension
, use the getNotificationData
method to retrieve content:
class NotificationService: UNNotificationServiceExtension {
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
/// Launch SDK
...
if let notificationId = request.content.userInfo["notification_id"] as? String {
FTRClient.shared.getNotificationData(notificationId, success: { data in
let payload = data.payload
contentHandler(request.content)
}, failure: { error in
contentHandler(request.content)
})
} else {
contentHandler(request.content)
}
}
}
In order to call the SDK method getnotificationdata
within an app extension such as UNNotificationServiceExtension
, the App Groups
and Keychain Access Group
need to be configured as described in the section Configuring App Group and Keychain Access Group.
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.
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.
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.
After a successful SDK PIN validation, the SDK PIN failed attempts counter is reset to 0.
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.
Errors Related to SDK PIN
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 apinAttemptsLeft
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. The enrollment flow can happen with user input like scanning the QR to get the activation code and manual input of activation shortcode or silently without user input by securely communicating an enrollment code to the app and calling the SDK’s enroll method behind the scenes.
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
orLockConfigurationType.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 functionactivateBiometrics
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
//
}
Starting from SDK version 3.3.0, you can configure the behavior for changing the SDK PIN using the SDKPinConfiguration
object within the LockConfiguration
object during the SDK launch:
let lockConfiguration = LockConfiguration(type: .biometricsOnly,
unlockDuration: 60,
invalidatedByBiometricsChange: true,
pinConfiguration: SDKPinConfiguration(allowPinChangeWithBiometricUnlock: false, deactivateBiometricsAfterPinChange: true))
- allowPinChangeWithBiometricUnlock: If set to true (default is false), the SDK allows changing the SDK PIN when the user is authenticated using biometrics.
- deactivateBiometricsAfterPinChange: If set to false (default is true), the SDK will not deactivate biometrics after a successful SDK PIN change.
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:
- An Auth API is invoked by the customer backend to start QR code authentication, with
qr_code
factor. Auth Api reference. - A push notification is sent to the SDK instances where the user account, for which the authentication was started for, is enrolled.
- The SDK calls
FTRNotificationDelegate
’s methodqrCodeScanRequested(_:_:_:)
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.
Universal Links
The SDK is able to handle Universal Links, which can be used to either enroll or authenticate users. There are two types of schemes supported: URI Schemes and URL Schemes.
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.
URL Schemes
URL schemes, that are a subset of URIs, refer to HTTPS links that native mobile apps can open after proving their association with a domain by hosting an apple-app-site-association
file. To implement URL schemes in your app, refer to the official Apple Developer guide on setting up universal links here.
The usage for URL schemes follows the same approach as URI schemes. Once your app handles the URL intents, you can also use the same openURL
method as shown above to process the link.
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:
- the same or a different device when restoring the device via an iCloud or encrypted iTunes backup, or
- 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 themigrateAccounts
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.
Errors Related to Automatic Account Recovery
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.
- 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.
- Futurae enrollment. To enroll, pass the binding token as the
bindingToken
parameter to theenroll
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.
- 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. - Futurae account recovery. For account recovery, pass the binding token as the
bindingToken
parameter to themigrateAccounts
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.
The enableAdaptive(delegate:)
function will enable all adaptive mechanisms, such adaptive data collection and submission of Adaptive contextual data for Authentication and Account Recovery.
Starting with the SDK version v3.2.4, you can use the following methods for enabling and disabling specific adaptive functionalities:
enableAdaptiveCollections(delegate:)
- Use this method instead of enableAdaptive(delegate:)
to enable collecting adaptive observations without enabling submission of Adaptive contextual data for Authentication or Account Recovery.
enableAdaptiveSubmissionOnAuthentication()
- Use this method to enable submission Adaptive contextual data for Authentication. Adaptive collection needs to be also enabled via enableAdaptiveCollections(delegate:)
.
enableAdaptiveSubmissionOnAccountMigration()
- Use this method to enable adaptive submissions for Account Recovery. Adaptive collections need to be also enabled via enableAdaptiveCollections(delegate:)
.
Each of the above methods has a corresponding method for disabling the functionality: disableAdaptiveCollections
, disableAdaptiveSubmissionOnAuthentication
, disableAdaptiveSubmissionOnAccountMigration
.
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 backendSDKApiErrorCode.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 a successful call to reset
function, all the enrolled accounts will be removed. To use accounts after reset
, re-launch the SDK and reenroll the accounts.
FTRClient.shared.reset(
appGroup: String? = nil,
keychain: FTRKeychainConfig? = nil,
lockConfiguration: LockConfiguration
)
The SDK may be reset in the following situations:
- To change the SDK lock configuration
type
,unlockDuration
orinvalidatedByBiometricsChange
, the SDK needs to be reset first and then initialized again with the new settings. - When the SDK
invalidatedByBiometricsChange
istrue
, the SDK may need to be reset and initialized again, depending on the SDK lock configuration type (see User presence verification section). - When the SDK is permanently locked due to consecutive failed SDK PIN verification attempts.
- If there are unexpected issues, you may try to reset and re-launch the SDK.
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:
authenticationURLOpened(_:)
Notifies the delegate that an authentication URL has been opened.activationURLOpened(_:)
Informs the delegate that an activation URL has been opened.openURLError(_:)
Called when an error occurs while processing the opened URL.
FTRNotificationDelegate
The FTRNotificationDelegate
protocol allows the delegate to be informed about events received via push notifications.
Methods:
approveAuthenticationReceived(_:)
Notifies the delegate of an approval for an authentication attempt received via push notification.unenrollUserReceived(_:)
Informs the delegate that a user’s account has been unenrolled.notificationError(_:)
Called when an error occurs while processing the push notification payload.qrCodeScanRequested(::_:)
Notifies the delegate that a QR code authentication has been requested for an enrolled account.notificationDataReceived(_:)
Notifies the delegate that the data for a Custom In-App Messaging notification has been received.
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:
SDKError
SDKAccountsError
SDKApiError
SDKAppAttestError
SDKBiometricsStateError
SDKConnectionError
SDKCryptoError
SDKDatabaseError
SDKLockError
SDKMigrationError
SDKPinError
SDKSwitchLockError
SDKSystemError
SDKURLError
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
andfailure
callback: these methods are compatible with Objective-C and Swift code. - Methods that return an
AsyncTask
orAsyncTaskResult
: these methods are only compatible with Swift code.AsyncTask
is a wrapper returned for operations that do not return a value (or returnVoid
) whereasAsyncTaskResult
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)
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
updateSDKConfig
decryptExtraInfo
enroll
replyAuth
getSessionInfo
getOfflineQRVerificationCode
(this method is protected when the sdkPin is not provided as argument)getTOTP
(this method is protected when the sdkPin is not provided as argument)logoutAccount
getSynchronousAuthToken
getAccountHistory
activateBiometrics
deactivateBiometrics
changeSDKPin
openURL
handleNotification
(protected if the payload contains extra info, otherwise not)migrateAccounts
(protected only ifLockConfigurationType.biometricsOnly
orLockConfigurationType.biometricsOrPasscode
)
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).