Findface Liveness iOS SDK

Getting started

Requirements

  • iOS 13.2 or later
  • latest XCode version (to upload the app to the AppStore)

Resources

Installation

This framework could be installed by adding LivenessAPI.framework to the desired project.

Basic usage

After installing the framework either via cocoapods or just cloning the sourcecode to your workspace, first you'd import the SDK:

import LivenessAPI

Importing the framework gives you an access to FFLApi class, which is the main class that is used to interact with FindFace Liveness API and the one, that exposes all methods, types and properties available at the moment, as well as to FFLOnboarding, FFLIdentification and FFLVerification classes, that implement common tasks using the abovementioned FFLApi.

All those classes (and other useful objects) are described in SDK API Docs. Also you can find some of the main usecases further below.

Initialization

First of all you should initialize the api object as follows:

let api = FFLApi(storage: <KVStorage>, 
                 rootUrl: <String>, 
                 login: <String>, 
                 password: <String>)

storage: KVStorage — storage object conforming to KVStorage protocol

root​Url: String — String object with the API server url

login: String — server login

password: String — server password

Those are the main parameters, there are some other ones that could be specified, visit SDK API Docs and view XCode quick help documentation for further information.

The API instance uses FFLApiToken from the storage specified, if any saved token is available. Otherwise it performs login using specified credentials.

To use another login and password, it is necessary to create another FFLApi instance, this is crucial for onboarding, verification and identification to work properly.

This SDK could work with the proxy server that forwards and processes calls to the Liveness API server. In order to do this, you should specify the proxy server rootUrl instead. The proxy server API used should be compatible/identical with the Liveness API server.


Unlike Android library version, the FFLApi instance doesn't connect on initialization, you should call checkToken method explicitly to connect.

checkToken

checkToken method is used to check saved token (if any) validity and perform login procedure using login and password to get new token if needed.

api.checkToken({ (result) in
    switch result {
    case .success(let user):
        // either succesfully got token from the API server, or the stored token is still valid
    case .failure(let error):
        // error occured
    }
})

Further steps

After performing all the basic setup described above, you are ready to use all FFLApi methods directly according to the desired business logic. However, there are additional helper classes that incapsulate some common required logic, as described below.

KVStorage

Storage is used by the SDK to store API token. It uses the key "FFLApiToken" internally to save and retrieve the saved token. You could use any other key to store your data (for example liveness-related) if you'd like to do so.

You could use any storage of your preference that conforms to KVStorage protocol. Here is the basic implementation of the class that conforms to KVStorage protocol, using UserDefaults as an actual storage.

class UserDefaultsStorage: KVStorage {
    func string(forKey: String) -> String? {
        UserDefaults().string(forKey: forKey)
    }

    func setValue(value: String, forKey: String) {
        UserDefaults().setValue(value, forKey: forKey)
        UserDefaults().synchronize()
    }

    func removeObject(forKey: String) {
        UserDefaults().removeObject(forKey: forKey)
    }
}

FFLOnboarding

This is the first of three helper classes, that implements basic user onboarding flow. It's purpose is to create a user dossier (in terms of Findface Platform).

Onboarding is performed in the following steps:

  • init — create instance
  • addDocument — add document photo (from jpeg file)
  • addSelfieMovie — add selfie movie (from mp4 file)
  • createDossier — create a dossied by selecting unused login.

On every step application should repeat the step until succeed, re-taking photos, movies or asking user to select another login instead of already used.

Note that if some of these steps, namely addDocument, addSelfieMovie and createDossier, fail — it is ok to repeat any of them until successed. It is not necessary to repeat the whole sequence, for example, if the login has already been performed, just call createDossier one more time.

Initialization

let onboarding = FFLOnboarding(api: <FFLApi>, 
                               threshold: <Double>)  

api: FFLApiFFLApi instance. Usually it would be the shared instance for the application.

threshold: Double — tolerance parameter to use to check document and face similarity during the onboarding process. As for example it is 0.7 by default in the demo app.

addDocument

For this and addSelfieMovie step you would need to be able to take a photo and a video using the phone camera. It could be achieved in a number of ways. For the sake of completeness of the flow, CameraCapture class object could be used. It is part of the demo application. It's usage would be described in the Annex 1 — CameraCapture class.

Generally speaking, photo and video size and format required may depend on the server configuration, there are some recommendations:

  • AVCaptureSession session.sessionPreset = .hd1280x720 — should work fine for both photo and video
  • AVCaptureMovieFileOutput movieOutput.setOutputSettings([AVVideoCodecKey: AVVideoCodecType.h264], for: connection!)h264 sould be fine by default

There is a default implementation example in the Annex 1.

This is the first onboarding step. Having initialized onboarding as a FFLOnboarding instance, you could use the following code to add a document:

onboarding.addDocument(fileData: fileData) { [unowned self] (result) in
    switch result {
        case .Ok:
            ...
        case .faceExists:
            ...
        case ...:
            ...
        default:
            ...
    }
}

fileData — image Data object, you get from the camera.

result — callback closure parameter, result. You process possible outcomes, for example, in the switch statement, having .Ok as a succesful outcome and processing other possible options as needed.

addSelfieMovie

The second onboarding step. Having initialized onboarding as a FFLOnboarding instance, you could use the following code to add a selfie movie:

onboarding.addSelfieMovie(videoFile: videoFileURL) { (result) in
    switch result {
        case .Ok:
            ...
        case .differentFaces:
            ...
        case .badVideo(code: .NOT_LIVE):
            ...
        default:
            ...
    }
}

videoFile — video file local URL. Generally you take a selfie video and store the video locally in a temporary file. This is the URL pointing to that locally stored video file.

result — callback closure parameter, result. You process possible outcomes, for example, in the switch statement, having .Ok as a succesful outcome and processing other possible options as needed.

createDossier

The third onboarding step. Having initialized onboarding as a FFLOnboarding instance, you could use the following code to create a user dossier:

onboarding.createDossier(login: login, password: password) { (result) in
    switch result {
        case .Ok:
            ...
        case .loginExists:
            ...
        default:
            ...
    }
}

login — as the name implies, the desired user login.

password — as the name implies, the desired user password.

result — callback closure parameter, result. You process possible outcomes, for example, in the switch statement, having .Ok as a succesful outcome and processing other possible options as needed.

After this step completes succesfully, the onboarding flow is finished.

There is a demo application for further details and flow example.

Onboarding result

Possible onboarding results are the following:

public enum OnboardingResult {
    case Ok
    case badVideo(code: FFLApi.LivenessResult)
    case lowPhotoQuality
    case differentFaces
    case faceExists
    case loginExists
    case networkError(error: FFLApi.NetworkError)
    case error(error: Error)
    case unexpectedError(code: String = "unknown", message: String = "unknown error")
}
  • Ok means successful completion of the step
  • lowPhotoQuality document photo is too bad
  • badVideo the selfie video is bad. The reason for the error is described with the code provided.
  • differentFaces the face found on the selfie video does not resemble the one on the document
  • faceExists the face is already associated to some profile
  • loginExists the login name is already in use
  • networkError network error occured, error details are available
  • error SDK error occured, error details are available
  • unexpectedError for any other error that normally should not happen

badVideo code

Code that represents the result of liveness check, with a video quality error report.

enum LivenessResult: Int {
    case OK
    case NO_FACE
    case SEVERAL_FACES
    case FACE_TOO_BIG
    case FACE_TOO_SMALL
    case FACE_CLOSE_TO_BORDER
    case FACE_OVEREXPOSED
    case FACE_UNDEREXPOSED
    case NOT_LIVE
    case UNKNOWN_ERROR
}

Those cases correspond to the ones described in the FindFace Liveness server documentation.

  • OK — Everything is ok, live face has been detected.
  • NO_FACE — live face was not found
  • SEVERAL_FACES — there are more than one face in the video
  • FACE_TOO_BIG — face is too big, camera might be too close
  • FACE_TOO_SMALL — face is too small, camera might be too far
  • FACE_CLOSE_TO_BORDER — face in the movie is too close to the border of the frame
  • FACE_OVEREXPOSED — too many light to process the frame
  • FACE_UNDEREXPOSED — light is too low to process the frame
  • NOT_LIVE — the face detected is not live
  • UNKNOWN_ERROR — any other unclassified error

FFLIdentification

Identification instance for a given API.

Identification is a process of finding a dossier by checking and scanning live selfie movie against the dossier available on fflsecurity server. See perform for details.

Identification is performed in the following steps:

  • init — create instance
  • perform — perform the user identification

Initialization

let identification = FFLIdentification(api: <FFLApi>)  

api: FFLApiFFLApi instance. Usually it would be the shared instance for the application.

perform

Looks for the Dossier associated with a live face presented in the selfie file after ensuring it is a live person video. See FFLApi.LiveIdentificationResult for explanation of resulting values. The method could be safely called multiple times on the same instance.

identification.perform(videoFile: videoFileURL) { (result) in
    switch result {
        case .success(let dossier):
            ...
        default:
            ...
    }
}

videoFile — video file local URL. Generally you take a selfie video and store the video locally in a temporary file. This is the URL pointing to that locally stored video file.

After succesful identification, result success case contains Dossier instance that has been found.

FFLVerification

Verification is a process of checking that the login (and optionally also a password) matches the live person face selfie movie.

Verification is performed in the following steps:

  • init — create an instance with the API object
  • login — login with valid credentials
  • verify — verify a freshly captured selfie

Initialization

let verification = FFLVerification(api: <FFLApi>, 
                                   threshold: <Double>)  

api: FFLApiFFLApi instance. Usually it would be the shared instance for the application.

threshold: Double — tolerance parameter to use to check stored and live face similarity during the verification process. As for example it is 0.7 by default in the demo app.

login

Login, checking the credentials provided. Be sure to get a non-nil dossier. It will check if the login is ok, and, optionally, the password. Repeat until the process succeeds.

verification.login(login: login, password: password, { (dossier) in
    if dossier == nil {
        // wrong login or password
    } else {
        // success
    }
})

login — as the name implies, the user login.

password — as the name implies, the user password.

dossier — if dossier is not nil, the user with login and password specified exists. dossier is set to the user's Dossier. Otherwise, credentials were wrong, or user doesn't exist.

verify

Verify a freshly captured selfie and process its result. It checks that the video is live and contains an image of the person that matches documents registered with the dossier. Repeat the process if needed.

verification.verify(videoFile: videoFileURL) { (result) in
        switch result {
        case .success(let dossier):
            ...
        default:
            ...
        }
    }
})

videoFile — video file local URL. Generally you take a selfie video and store the video locally in a temporary file. This is the URL pointing to that locally stored video file.

After succesful verification, result success case contains Dossier instance that has been verified.

Annex 1 — CameraCapture class

You may find a CameraCapture class in the demo application or it may be provided with the SDK distribution. It encapsulates the main photo and video capturing API, providing two main methods makePhoto and takeVideo.

Basically the class is self-explaining and describing ways of capturing photos and videos in iOS is not an intended goal of this document, so the following is a quick how-to.

First, you initialize CameraCapture instance:

var cameraCapture = CameraCapture(containerView: cameraContainerView, cameraPosition: .front)

here cameraContainerView is a UIView in the view hierarchy that is used as a camera preview, and cameraPosition indicates which camera to use (front or back one).

Then you start and stop camera preview in viewWillAppear and viewWillDisappear respectively:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    cameraCapture?.startCamera()
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    cameraCapture?.stopCamera()
}

To correctly support interface changes (rotation for example) you override viewDidLayoutSubviews:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    cameraCapture?.setupPreviewFrame()
}

Then you could capture photo or movie when you are ready:

cameraCapture?.makePhoto { (imageData) in ... }
cameraCapture?.takeVideo(file: filename, { (file) in ... })

filename here is the local URL where you desire to store taken movie file.

Then you process taken photo or video as needed inside the callback closure.