Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/signing-sdk/face-auth-ios/llms.txt

Use this file to discover all available pages before exploring further.

The signing flow authenticates a previously registered user by prompting them to satisfy their stored FIDO2 passkey — typically via Face ID or device biometrics — and returns an ES512-signed JWT along with a requestId. Your backend verifies the JWT using the ES512 public key to confirm the user’s identity before accepting the signing action.

What signing does

  1. Passkey authentication — the SDK locates the credential stored during registration (bound to your rpId) and asks the OS to authorize it via Face ID or device unlock.
  2. JWT issuance — once the passkey challenge is satisfied, the backend issues an ES512-signed JWT containing the user’s identity claim.
  3. Result delivery — your completion handler receives both the jwt string and a requestId you can use for audit purposes.

Required inputs

ParameterTypeDescription
bankIdStringMust match the identifier used at registration exactly.
dto[String: String]Optional extra fields (e.g. ["otp": otp]). Pass [:] if unused.
configTadSigningConfigSame TadSigningConfig instance used for registration.
modeTadSigningModeMust be .sign for the signing flow.

Presenting the view controller

1

Build the dto payload

Construct the optional dictionary. Include an OTP when your flow requires a second factor alongside the passkey.
let dto = otp.isEmpty ? [:] : ["otp": otp]
2

Instantiate TadSigningViewController in .sign mode

Create the view controller with mode: .sign. The bankId must be the same value supplied during registration — the SDK uses it to locate the correct passkey.
let signingVC = TadSigningViewController(
    config:  SDKConfig.shared,
    bankId:  bankId,
    dto:     dto,
    mode:    .sign
) { signingResult in
    isLoading = false
    switch signingResult {
    case .success(let jwt, let requestId):
        print("Signed. requestId: \(requestId)")
        print("JWT: \(jwt)")
    case .failure(let code, let message):
        print("Error \(code.rawValue): \(message)")
    }
}
3

Resolve the top view controller

Walk the presentation chain to find the currently active UIViewController.
private func topViewController() -> UIViewController? {
    guard let scene = UIApplication.shared.connectedScenes
        .compactMap({ $0 as? UIWindowScene })
        .first(where: { $0.activationState == .foregroundActive }),
          let root = scene.windows.first(where: { $0.isKeyWindow })?.rootViewController
    else { return nil }

    var top = root
    while let presented = top.presentedViewController { top = presented }
    return top
}
4

Present the view controller

Set your loading state, then present signingVC modally. The SDK handles passkey selection and OS-level biometric prompts automatically.
guard let vc = topViewController() else { return }
isLoading = true
vc.present(signingVC, animated: true)

Handling the result

On success, both jwt and requestId are populated. The demo app displays them together:
switch signingResult {
case .success(let jwt, let requestId):
    resultColor = .green
    result = "✅ Signed\nrequestId: \(requestId)\n\nJWT:\n\(jwt)"
case .failure(let code, let message):
    resultColor = .red
    result = "❌ \(code.rawValue)\n\(message)"
}
Send the jwt to your backend for verification. The requestId can be stored alongside the signing event for auditing.
Always verify the JWT on your backend before trusting it. Use the ES512 public key from TadSigningConfig.publicKeyPem to verify the signature with your preferred JWT library. A JWT that fails signature verification should be rejected regardless of its claims. See the security model page for key management details.

Difference from registration

Registration (.register)Signing (.sign)
Face liveness checkYesNo — passkey satisfies authentication
Passkey createdYesNo — existing passkey is used
jwt in resultEmpty stringPopulated ES512-signed JWT
requestId in resultPopulatedPopulated

Permissions

Because the signing flow uses the device’s stored passkey rather than the camera, no camera permission is required. Face ID is invoked by the OS as part of passkey authorization and uses the existing NSFaceIDUsageDescription key you declared during setup:
<key>NSFaceIDUsageDescription</key>
<string>Used to secure your Passkey during registration and sign-in</string>

Build docs developers (and LLMs) love