PIN, face and liveness verification
This page explains how to integrate PIN and face liveness verification using PinManager and FaceManager.
What This Covers
- creating and updating PIN with
PinManager - verifying PIN (
PinUseCase.verify) for authentication challenges - creating face verification screens with
FaceManager - passing liveness settings through
FaceConfiguration - handling notification-driven authentication prompts through
RequestAuthenticationDelegate - mapping and handling SDK errors (
PinManagerError,FaceManagerError)
API Surface
PIN APIs
mobileID.pinManagerPinManager.isPinSet: BoolPinManager.makePinViewController(for:onPinPhaseFinishedHandler:) -> PinViewController?PinUseCase:.set,.verifyPinPhase:.verification,.creationFirstStep,.creationSecondStepPinViewControllerPinManagerErrorKeypadType:.default,.custom(withProcessButton:)
Face/Liveness APIs
mobileID.faceManagerFaceManager.isFaceSet() throws -> BoolFaceManager.makeFaceVerificationViewController(for:with:handler:) async throws -> FaceViewControllerFaceConfigurationFaceViewControllerFaceManagerErrorLivenessSettingsand enums:LivenessSettings.LivenessTypeLivenessSettings.SecurityLevel
Authentication Counterparts (Notification Flows)
MobileIDNotification.accept(using:delegate:)RequestAuthenticationDelegateAuthenticationRequest
When request authentication is started by the SDK (for example from notifications), the SDK provides either PinViewController or FaceViewController to your delegate, and your app is responsible for presenting/dismissing those screens.
End-To-End Flow
PIN Integration
1. Build The PIN Screen
Swift1import MobileIDCore2import UIKit34@MainActor5func presentPinSetup(mobileID: MobileID, from navigationController: UINavigationController) {6 guard let pinVC = mobileID.pinManager.makePinViewController(for: .set, onPinPhaseFinishedHandler: { result in7 switch result {8 case .success(.verification):9 // Existing PIN was verified (only happens when updating an already-set PIN).10 break11 case .success(.creationFirstStep):12 // First PIN entry accepted, waiting for confirmation entry.13 break14 case .success(.creationSecondStep):15 // PIN creation/update completed.16 break1718 case .failure(let error):19 // Show UI message and recover accordingly.20 print("PIN flow error: \(error)")21 }22 }) else {23 // Expected only if useCase is .verify and no PIN is set.24 return25 }2627 pinVC.keypadType = .custom(withProcessButton: true)28 pinVC.feedbackView = {29 // Your feedback view30 }3132 pinVC.onPinChange = { phase, digits in33 print("PIN phase=\(phase), digits=\(digits)")34 }3536 navigationController.pushViewController(pinVC, animated: true)37}
2. Trigger Processing And Restart Correctly
- If you use
.custom(withProcessButton: true), the built-in process button triggersprocessPin(). - If you use another keypad mode, call
await pinViewController.processPin()when your UX decides submission is ready. - After a handled success/failure that should continue the same PIN flow, call
await pinViewController.restart()before next user interaction.
Face + Liveness Integration
Prerequisites
- Include and link the face matching plugin module.
- Ensure
captureSDKConfigurationis present in yourMobileIDConfiguration. - Ensure the user completed face enrollment if your flow requires a stored template.
1. Check Readiness
Swift1let hasFace = try mobileID.faceManager.isFaceSet()
isFaceSet() can throw FaceManagerError.faceMatchingModuleNotFound if the face module is not linked correctly.
2. Create And Present Face Verification
Swift1import MobileIDCore2import MobileIDTheme3import UIKit45@MainActor6func presentFaceVerification(7 mobileID: MobileID,8 navigationController: UINavigationController,9 theme: ThemeConfiguration,10 livenessSettings: LivenessSettings,11 template: Data?12) async {13 let config = FaceConfiguration(14 title: "Verify your identity",15 livenessSettings: livenessSettings,16 theme: theme17 )1819 let handler: (Result<UIImage, Error>) -> Void = { result in20 switch result {21 case .success(let capturedImage):22 print("Face challenge passed, image size=\(capturedImage.size)")23 case .failure(let error):24 print("Face challenge failed: \(error)")25 }26 }2728 do {29 let faceVC = try await mobileID.faceManager.makeFaceVerificationViewController(30 for: config,31 with: template,32 handler: handler33 )34 navigationController.pushViewController(faceVC, animated: true)35 } catch {36 print("Unable to create face verification screen: \(error)")37 }38}
Notes:
- If
templateisnil, SDK uses the portrait template saved during face enrollment. - For online authentication requests, the SDK internally uses
captureSDKConfiguration.settings.onlineAuthenticationwhen it orchestrates face prompts.
Liveness Settings Reference
LivenessSettings controls challenge behavior:
livenessType:.active,.passive,.passiveVideo,.noLivenesstargetsNumber: relevant for.active(valid range in SDK:1...3)securityLevel:.low,.medium,.highnumberOfAttempts: attempts before failurefaceAcquisitionTimeout: milliseconds (>= 5000)faceMatchingThreshold: when matching score is required (SDK validation range:2500...5000)isIdleTimerDisabled: idle timer behavior for challenge UX
Use CustomLivenessSettings (through CaptureSDKConfiguration) when overriding defaults per scenario.
Notification-Driven Authentication (PIN/Face Prompts)
For request acceptance flows, use RequestAuthenticationDelegate.
Swift1import MobileIDCore2import UIKit34final class RequestAuthDelegate: RequestAuthenticationDelegate {5 private weak var navigationController: UINavigationController?67 init(navigationController: UINavigationController) {8 self.navigationController = navigationController9 }1011 func notification(_ request: AuthenticationRequest, promptedForAuthentication authenticationPrompt: PinViewController) {12 navigationController?.pushViewController(authenticationPrompt, animated: true)13 }1415 func notification(_ request: AuthenticationRequest, promptedForAuthentication authenticationPrompt: FaceViewController) {16 navigationController?.pushViewController(authenticationPrompt, animated: true)17 }1819 func didFinishAuthentication(of request: AuthenticationRequest) {20 navigationController?.popToRootViewController(animated: true)21 }2223 func notificationDidBecomeBusy(_ request: AuthenticationRequest, expectedInterval: NavigatorBusyStateInterval) {24 // Show loader.25 }2627 func notificationDidBecomeIdle(_ request: AuthenticationRequest) {28 // Hide loader.29 }30}
Error Handling Cheat Sheet
PIN (PinManagerError)
pinInvalid: wrong PIN enteredpinValuesDoNotMatch: PIN confirmation mismatchproposedPINDoesNotMeetRequirements: blacklisted/invalid PIN policypinLengthExceedsBounds: entered PIN outside allowed policy boundspinDigitIgnoredDueToPinLengthBounds: extra digit ignoredincorrectPinPolicyConfiguration: wallet/policy configuration issuenotPreparedForInteraction: flow not restarted/prepared before user interactioncouldNotConnectToServer: network issue after too many failed attemptsverificationTemporaryBlocked(backoffTimeInSeconds:): temporary lockout; retry after backoff
Face (FaceManagerError)
faceMatchingNotConfigured: missingcaptureSDKConfigurationfaceMatchingModuleNotFound: missing/unlinked face matching module
Implementation Guidance
- Use
.setfor first-time PIN creation and for PIN updates (update starts with verification when PIN already exists). - Expect
makePinViewController(for: .verify, ...)to returnnilif PIN is not set yet. - Keep PIN/face prompt presentation centralized in one coordinator to avoid duplicate navigation logic.
- Always provide user-facing recovery for lockout/backoff and configuration errors.
- Keep liveness customization server/environment aligned with your risk policy and UX goals.
Related APIs
MobileIDMobileIDNotificationRequestAuthenticationManagerCaptureSDKConfigurationCaptureSDKSettingsCustomLivenessSettings