Skip to content
intermediate Phase 12 · Security

Face ID & Touch ID

Integrate biometric authentication with LocalAuthentication framework.

40m
2 problems
Topic Progress 0%

LocalAuthentication Framework

What is LocalAuthentication?

The LocalAuthentication framework provides a simple API for evaluating biometric policies (Face ID, Touch ID) and device passcode. It abstracts the hardware differences between devices.

Basic Biometric Authentication

import LocalAuthentication

class AuthManager {
    func authenticate(reason: String) async throws -> Bool {
        let context = LAContext()
        var error: NSError?
        
        guard context.canEvaluatePolicy(
            .deviceOwnerAuthenticationWithBiometrics,
            error: &error
        ) else {
            throw AuthError.biometricsUnavailable
        }
        
        let success = try await context.evaluatePolicy(
            .deviceOwnerAuthenticationWithBiometrics,
            localizedReason: reason
        )
        return success
    }
}

Checking Biometric Type

func biometricType() -> LABiometryType {
    let context = LAContext()
    var error: NSError?
    _ = context.canEvaluatePolicy(
        .deviceOwnerAuthenticationWithBiometrics,
        error: &error
    )
    
    switch context.biometryType {
    case .faceID: return .faceID
    case .touchID: return .touchID
    case .opticID: return .opticID
    @unknown default: return .none
    }
}

Error Handling

func handleLAError(_ error: NSError) -> String {
    switch LAError.Code(rawValue: error.code) {
    case .userCancel:
        return "Authentication cancelled by user"
    case .userFallback:
        return "User chose fallback authentication"
    case .biometryLockout:
        return "Biometrics locked due to too many failures"
    case .biometryNotEnrolled:
        return "No biometrics enrolled"
    case .biometryNotAvailable:
        return "Biometrics not available on this device"
    default:
        return "Authentication failed"
    }
}

Biometric Policies

Authentication Policies

LocalAuthentication provides two main policies:

  • deviceOwnerAuthenticationWithBiometrics: Biometric only, no passcode fallback
  • deviceOwnerAuthentication: Biometric or device passcode
// Biometric only (no passcode fallback)
let biometricOnly = context.evaluatePolicy(
    .deviceOwnerAuthenticationWithBiometrics,
    localizedReason: "Unlock vault"
)

// Biometric or passcode
let biometricOrPasscode = context.evaluatePolicy(
    .deviceOwnerAuthentication,
    localizedReason: "Confirm payment"
)

Requiring Passcode After Biometric Failure

After multiple biometric failures, iOS automatically falls back to passcode with deviceOwnerAuthentication:

func authenticateForSensitiveAction() async throws {
    let context = LAContext()
    context.touchIDAuthenticationAllowableReuseDuration = 10
    
    do {
        let success = try await context.evaluatePolicy(
            .deviceOwnerAuthentication,
            localizedReason: "Authorize transfer of $500"
        )
        if success {
            // Proceed with sensitive action
        }
    } catch {
        // Handle error, show passcode fallback UI
    }
}

Evaluating Policy with Retry

func authenticateWithRetry(maxAttempts: Int = 3) async throws -> Bool {
    var attempts = 0
    
    while attempts < maxAttempts {
        let context = LAContext()
        do {
            let success = try await context.evaluatePolicy(
                .deviceOwnerAuthenticationWithBiometrics,
                localizedReason: "Authenticate to continue"
            )
            if success { return true }
        } catch {
            attempts += 1
            if attempts >= maxAttempts {
                throw AuthError.maxAttemptsReached
            }
        }
    }
    return false
}

Fallback Handling

Custom Fallback Title

You can customize the fallback button text shown to users:

let context = LAContext()
context.localizedFallbackTitle = "Use Passcode"
context.localizedCancelTitle = "Cancel"

Fallback to Passcode Entry

When biometrics fail or the user taps fallback, handle passcode entry:

func authenticateWithFallback() async throws -> Bool {
    let context = LAContext()
    
    do {
        return try await context.evaluatePolicy(
            .deviceOwnerAuthentication,
            localizedReason: "Authenticate to access your account"
        )
    } catch LAError.userFallback {
        // Show custom passcode entry screen
        return await showPasscodeEntry()
    } catch LAError.biometryLockout {
        // Biometrics locked, require passcode
        return await showPasscodeEntry()
    } catch {
        throw error
    }
}

Biometric Changes Detection

Handle biometric changes like enrolling a new face or fingerprint:

func checkBiometricChange() {
    let context = LAContext()
    var error: NSError?
    
    guard context.canEvaluatePolicy(
        .deviceOwnerAuthenticationWithBiometrics,
        error: &error
    ) else { return }
    
    // Evaluate with LAPolicyDeviceOwnerAuthenticationWithBiometrics
    // If biometry changes, the evaluation fails and you should
    // require re-authentication and potentially re-encrypt stored keys
    
    let state = context.evaluatedPolicyDomainState
    // Store this state and compare on next launch
    // If it changed, biometrics were modified
}

Privacy Requirements

Before using Face ID, add the NSFaceIDUsageDescription key to Info.plist with a clear explanation of why your app needs Face ID:

<key>NSFaceIDUsageDescription</key>
<string>We use Face ID to authenticate you and protect your financial data.</string>

Touch ID does not require a usage description but it is good practice to inform users.

Best Practices

  • Always provide a passcode fallback option
  • Handle all LAError cases gracefully
  • Use localizedReason to explain why authentication is needed
  • Store biometric state to detect enrollment changes
  • Never store biometric data yourself, use the system framework

Quiz

1. What is the difference between deviceOwnerAuthenticationWithBiometrics and deviceOwnerAuthentication?

Question 1 options

2. What key must you add to Info.plist for Face ID?

Question 2 options

3. What should you do when LAError.biometryLockout occurs?

Question 3 options

4. Why check evaluatedPolicyDomainState?

Question 4 options

Flashcards

Question

What are the two main LAContext evaluation policies?

Answer

deviceOwnerAuthenticationWithBiometrics (biometrics only) and deviceOwnerAuthentication (biometrics or passcode).

Question

What is localizedReason used for?

Answer

It provides the message displayed to the user explaining why biometric authentication is required, such as Authorize payment.

Question

When does LAError.biometryLockout occur?

Answer

After too many failed biometric attempts. The user must enter their passcode before biometrics can be used again.

Revision Notes

Key Takeaways

  • 1. LocalAuthentication provides a unified API for Face ID and Touch ID
  • 2. Always provide a passcode fallback option
  • 3. Handle biometryLockout by requiring passcode
  • 4. Check evaluatedPolicyDomainState to detect enrollment changes
  • 5. Add NSFaceIDUsageDescription to Info.plist for Face ID

Interview Tips

  • Explain the difference between the two LAContext policies
  • Describe how to handle biometric fallback gracefully
  • Discuss how to detect biometric enrollment changes
  • Walk through implementing biometric authentication for a banking app

Cheat Sheet

Biometrics Quick Reference

  • LAContext for biometric evaluation
  • canEvaluatePolicy: check availability
  • evaluatePolicy: perform authentication
  • .deviceOwnerAuthenticationWithBiometrics: no passcode fallback
  • .deviceOwnerAuthentication: allows passcode fallback
  • NSFaceIDUsageDescription required in Info.plist
  • Handle all LAError cases