Develop and test OpticID for Vision Pro

Develop and test OpticID for Vision Pro

Β·

3 min read

In this blog post, I'll explain how you, a Swift developer, can leverage Optic ID for authentication and how to test OpticID in the Vision Pro simulator.

Optic ID is a new, secure biometric system to authenticate Apple headset users. But this should not scare you. Because if you ever built an iOS app using FaceID, then you will recognize the APIs to interact with Optic ID 😊

Develop with OpticID

Apple's LocalAuthentication framework provides a universal API independent of which biometric authentication type is used. Therefore, you can use the existing APIs that you are familiar with.

First, you'll need to check if biometric authentication is possible on the device. It might be that the user did not enroll in OpticID. Do this by calling the canEvaluatePolicy(_:error:) method.

var error: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
    print(error?.localizedDescription ?? "Can't evaluate policy")
    // Fallback to ask for username and password.
    // ...
    return
}

Then, you can trigger the authentication by calling evaluatePolicy(\_:localizedReason:reply:).

do {
    try await context.evaluatePolicy(
        .deviceOwnerAuthenticationWithBiometrics,
        localizedReason: "Use Biometric Authentication to access the app")
    return "\(context.biometryType.name) authentication successful"
} catch LAError.userCancel {
    return "User canceled authentication"
} catch LAError.userFallback {
    return "User chose to use fallback authentication method"
} catch LAError.biometryLockout {
    return "Biometry locked due to too many failed attempts"
} catch {
    return "Authentication failed: \(error.localizedDescription)"
}

All the APIs you know from working with TouchID or FaceID also apply to OpticID.

The only addition I found was on LABiometryType. In the example above, I created an extension on this type to return a "localized" description. No surprise that you'll find a new enum value .opticID

extension LABiometryType {
    var name: String {
        switch self {
        case .none:
            return "None"
        case .touchID:
            return "Touch ID"
        case .faceID:
            return "Face ID"
        case .opticID:
            return "Optic ID"
        @unknown default:
            return "Unknown"
        }
    }
}

Here is a complete SwiftUI application example.

import LocalAuthentication
import SwiftUI

@main
struct OpticIDTestingApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @State var authenticationResult: String?
    let context = LAContext()

    var body: some View {
        VStack {
            Text("Biometric Authentication")
                .font(.title)
                .padding()

            Button("Authenticate") {
                Task {
                    authenticationResult = await authenticateWithBiometric()
                }
            }
            .padding()

            if let authenticationResult {
                Text(authenticationResult)
            }
        }
    }

    func authenticateWithBiometric() async -> String {
        let isSupportingBiometrics = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
        guard isSupportingBiometrics else {
            return "Biometric authentication not available on this device"
        }
        do {
            try await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Use Biometric Authentication to access the app")
            return "\(context.biometryType.name) authentication successful"
        } catch LAError.userCancel {
            return "User canceled authentication"
        } catch LAError.userFallback {
            return "User chose to use fallback authentication method"
        } catch LAError.biometryLockout {
            return "Biometry locked due to too many failed attempts"
        } catch {
            return "Authentication failed: \(error.localizedDescription)"
        }
    }
}

extension LABiometryType {
    var name: String {
        switch self {
        case .none:
            return "None"
        case .touchID:
            return "Touch ID"
        case .faceID:
            return "Face ID"
        case .opticID:
            return "Optic ID"
        @unknown default:
            return "Unknown"
        }
    }
}

Test OpticID in VisionPro simulator

The VisionOS simulator allows you to test OpticID.

Xcode 15 is still in beta, and you'll have to use the latest Beta 8 as it resolved several issues, e.g. even though Optic ID appears in the Simulator, it wasn't possible to simulate Optic ID enrollment. (112460069)

Test OpticID with the VisionPro simulator

Setting "Enrolled" before calling canEvaluatePolicy(_:error:) will ensure that true will be returned.

Choosing "Matching Eye" or "Non-matching Eye" after calling evaluatePolicy(\_:localizedReason:reply:) will complete the authentication process accordingly.

I created a YouTube video to illustrate the testing process better.

In the future, OpticID might even become relevant on iOS 🀯

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!