civil-and-structural-engineering
How to Implement Face Id and Touch Id Authentication in Ios
Table of Contents
Introduction to Biometric Authentication on iOS
Biometric authentication – Face ID and Touch ID – has transformed how users secure and access their iOS apps. By integrating Apple’s Local Authentication framework, you offer a frictionless yet highly secure login experience. Users can authorise purchases, unlock content, or sign into accounts with nothing more than a glance or a touch. This guide covers everything you need to know to implement Face ID and Touch ID in your iOS app using Swift, from project setup to production‑ready error handling and fallback mechanisms.
Biometrics are now a cornerstone of modern mobile UX. They eliminate the need to remember complex passwords, reduce friction, and dramatically lower the risk of account takeover. According to Apple, over 90% of devices in active use support either Face ID or Touch ID. Integrating them not only pleases users but also meets the security expectations of today’s marketplace.
Prerequisites
Before you start coding, ensure you have the following:
- macOS 11 (Big Sur) or later with Xcode 13 or higher.
- An iPhone or iPad with a supported biometric sensor:
- Touch ID – iPhone 5s or later, iPad Air 2 or later, iPad mini 3 or later.
- Face ID – iPhone X or later, iPad Pro (3rd generation) or later.
- Target iOS 13+ for full API support (the
LAPolicyand error handling used here are available from iOS 8 onward, but newer features like.deviceOwnerAuthenticationWithBiometricsand better fallback flows work best on iOS 13+). - Basic familiarity with Swift and Xcode projects, including editing
Info.plist. - An Apple Developer account (free accounts can run on physical devices, but you need a paid account for distribution).
Setting Up Your Xcode Project
Open your existing iOS project in Xcode, or create a new one (File → New → Project → App). We’ll add the necessary permissions and imports.
1. Add the Face ID Usage Description
Apple requires app Info.plist to contain the NSFaceIDUsageDescription key whenever your app uses Face ID. This string is displayed in the system prompt when the user is first asked to permit biometric authentication. Without it, your app will crash when you attempt to evaluate a Face ID policy.
To add the key:
- Open
Info.plistin the project navigator. - Click the + button next to any existing key.
- Type or select Privacy - Face ID Usage Description from the dropdown.
- For the value, enter a clear, brief reason. For example: "Authenticate to access your secure account details."
If your app supports only Touch ID, you can skip this key – but adding it won’t hurt and makes your app forward‑compatible with future devices.
2. Import the Local Authentication Framework
In the Swift file where you’ll call biometrics, add the following import at the top:
import LocalAuthentication
That’s all the setup needed. The framework’s LAContext class handles all the heavy lifting.
Implementing Biometric Authentication – Step by Step
We’ll build a reusable method that checks biometric availability, evaluates the policy, and returns a result with clear error handling.
1. Create an Authentication Manager
Start by creating a class or a standalone function. A dedicated manager helps keep your authentication logic testable and reusable across multiple view controllers.
import LocalAuthentication
class BiometricAuthManager {
private let context = LAContext()
/// Attempts biometric authentication and calls the completion with success or error.
func authenticateUser(reason: String, completion: @escaping (Result<Void, BiometricError>) -> Void) {
// Reset the context before evaluating – important for repeated use
context.invalidate()
var authError: NSError?
let canEvaluate = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError)
guard canEvaluate else {
completion(.failure(mapError(authError)))
return
}
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, evaluateError in
DispatchQueue.main.async {
if success {
completion(.success(()))
} else {
completion(.failure(self.mapError(evaluateError as NSError?)))
}
}
}
}
private func mapError(_ error: NSError?) -> BiometricError {
// We'll define BiometricError below
// ...
}
}
enum BiometricError: Error {
case biometryNotAvailable
case biometryNotEnrolled
case biometryLockout
case userCancel
case userFallback
case systemCancel
case appCancel
case invalidContext
case notInteractive
case passcodeNotSet
case unknown(NSError)
}
Let’s break down the key parts:
- LAContext – This object encapsulates the authentication state. We call
invalidate()before each use to clear any previous evaluation and prevent reuse of cached credentials. - canEvaluatePolicy – Checks whether the device can perform biometric authentication. It returns
trueonly if the device has a biometric sensor and the user has enrolled a face or fingerprint. If it returnsfalse, theerrorparameter tells us why. - evaluatePolicy – This triggers the actual system dialog. The
localizedReason(the same as your plist description for Face ID) is shown to the user. The closure returns a boolean success and an optional error.
2. Mapping Apple’s Error Codes
Apple provides error constants via LAError.Code. A robust implementation maps them to your own enum so you can provide actionable feedback to the user or developer.
private func mapError(_ error: NSError?) -> BiometricError {
guard let error = error else { return .unknown(NSError(domain: "BiometricAuth", code: -1, userInfo: nil)) }
let code = LAError.Code(rawValue: error.code) ?? .appCancel
switch code {
case .biometryNotAvailable:
return .biometryNotAvailable
case .biometryNotEnrolled:
return .biometryNotEnrolled
case .biometryLockout:
return .biometryLockout
case .userCancel:
return .userCancel
case .userFallback:
return .userFallback
case .systemCancel:
return .systemCancel
case .appCancel:
return .appCancel
case .invalidContext:
return .invalidContext
case .notInteractive:
return .notInteractive
case .passcodeNotSet:
return .passcodeNotSet
default:
return .unknown(error)
}
}
Common scenarios:
.biometryNotAvailable– No Face ID / Touch ID sensor on the device (e.g., simulator, iPod Touch)..biometryNotEnrolled– The sensor exists but the user hasn’t enrolled a face or fingerprint in Settings..biometryLockout– Too many failed attempts; the sensor is disabled until the user enters their device passcode..userCancel– The user tapped “Cancel” on the system dialog..userFallback– The user tapped “Enter Passcode” (if you’ve allowed fallback).
3. Handling the Authentication Result
In your view controller, call the authentication method and react to the result.
let authManager = BiometricAuthManager()
authManager.authenticateUser(reason: "Unlock the vault to view your saved passwords.") { result in
switch result {
case .success:
// Navigate to the secure screen or perform the protected action
self.showSecureContent()
case .failure(let error):
self.handleBiometricError(error)
}
}
private func handleBiometricError(_ error: BiometricError) {
switch error {
case .userCancel:
// Do nothing – user cancelled intentionally
break
case .userFallback:
// Show your custom passcode screen
self.presentPasscodeEntry()
case .biometryLockout:
// Inform the user that they must enter the device passcode
self.showAlert(title: "Biometric Locked", message: "Too many failed attempts. Please use your device passcode to unlock.")
case .biometryNotEnrolled:
self.showAlert(title: "Biometric Not Set Up", message: "Go to Settings > Face ID & Passcode (or Touch ID & Passcode) to enroll.")
case .biometryNotAvailable:
self.showAlert(title: "Biometric Not Available", message: "This device does not support Face ID or Touch ID.")
default:
self.showAlert(title: "Authentication Failed", message: "An unexpected error occurred. Please try again.")
}
}
Including a Fallback to Device Passcode
For best UX, always allow the user to fall back to the device passcode. Use the policy .deviceOwnerAuthentication instead of .deviceOwnerAuthenticationWithBiometrics. If biometrics are available, the system will still prompt for biometrics first, but it also shows an “Enter Passcode” button.
func authenticateWithPasscodeFallback(reason: String, completion: @escaping (Result<Void, Error>) -> Void) {
context.invalidate()
var error: NSError?
let canEvaluate = context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error)
guard canEvaluate else {
// If even passcode authentication is unavailable (e.g., no passcode set on device),
// you should guide the user to set a passcode.
completion(.failure(error ?? BiometricError.passcodeNotSet))
return
}
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, authError in
DispatchQueue.main.async {
if success {
completion(.success(()))
} else {
completion(.failure(authError ?? BiometricError.unknown(NSError())))
}
}
}
}
Note: If you use .deviceOwnerAuthentication, the system can also handle cases where biometrics are locked out by immediately prompting for the passcode. This is often the best choice for actions like app unlock or payments, as it provides a seamless path to recovery.
Testing Your Implementation
You cannot test biometric prompts on the iOS Simulator. Simulating a biometric match is possible using the simulator’s “Face ID / Touch ID” menu item (Hardware → Face ID or Hardware → Touch ID), but the actual system dialog won’t appear. For complete testing, you must run on a physical device.
Test the following scenarios:
- Successful authentication – present a matched face or fingerprint.
- Failed authentication – present a non‑matching biometric.
- User cancel – tap Cancel on the dialog.
- Lockout – fail biometrics multiple times until the system disables it.
- No biometrics enrolled – temporarily remove all fingerprints/faces in device Settings → Face ID & Passcode / Touch ID & Passcode.
- No passcode set – turn off the device passcode entirely (be careful – this makes the device less secure).
- Multiple rapid calls – ensure you handle context invalidation to avoid crashes.
Also test on both Face ID and Touch ID devices if you can. Face ID expects the user to look at the camera, while Touch ID requires a clean finger on the sensor. Your code is identical for both – Apple’s framework abstracts the hardware.
Best Practices and Security Considerations
- Never cache or store biometric data. The Local Authentication framework handles everything; your app never receives the actual face map or fingerprint. Only the boolean success/failure is exposed.
- Use biometrics for sensitive operations only. For example, unlocking an app, authorising purchases, or viewing private data. Avoid unnecessary biometric prompts for trivial actions.
- Provide a clear and honest
localizedReason. The system shows this to the user; make it descriptive enough that they understand why you’re requesting authentication. e.g., “Authenticate to view your health records.” - Handle
.userFallbackgracefully. When the user selects “Enter Passcode”, you should present your own authentication screen or use the device passcode via.deviceOwnerAuthentication. - Reset the context for each attempt. Call
context.invalidate()before evaluating a new policy to prevent unintended reuse of previous credentials. - Consider combining biometrics with a server‑side token. For high‑security features (e.g., bank transfers), use biometrics to authorise an access token stored in the Keychain, rather than relying solely on local authentication.
- Support both Face ID and Touch ID transparently. Write your code once – Apple’s framework adjusts the UI automatically (Face ID uses an oval animation on the notch, Touch ID shows a fingerprint icon).
- Test on devices with different screen sizes and post‑Face ID updates (e.g., iPad Pro with Face ID).
Advanced Topics and Further Reading
LocalAuthentication & the Keychain
You can combine biometric authentication with the Keychain to protect secrets. Use SecAccessControlCreateWithFlags with the .biometryCurrentSet or .userPresence constraint. This ensures that the Keychain item can only be accessed after a successful biometric (or passcode) check. Example:
let access = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
.userPresence, // biometrics or passcode
nil)
SwiftUI Integration
In SwiftUI, present the authentication flow from a .task modifier or a button action. Use the same LAContext API. For example:
Button("Unlock") {
let context = LAContext()
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Access your journals") { success, error in
if success { /* navigate */ }
}
}
Supporting Older iOS Versions
If you support iOS 8–12, note that LAError.Code values like .biometryLockout were added later. Always check availability with if #available(iOS 11.0, *) before using newer codes. For iOS 11+, you can also check the context.biometryType property to determine whether the device uses Face ID or Touch ID, which can be useful for custom UI:
switch context.biometryType {
case .faceID:
// Show Face ID icon
case .touchID:
// Show Touch ID icon
case .none:
// Biometrics unavailable
}
Conclusion
Integrating Face ID and Touch ID into your iOS app is straightforward with the Local Authentication framework. You now have a reusable, production‑ready authentication flow that handles availability checks, fallback to passcode, and all common error scenarios. Biometric authentication not only improves security – by reducing the risk of weak passwords – but also delights users with a fast, intuitive unlock experience. As you expand your app, remember to follow Apple’s Human Interface Guidelines for biometrics (Face ID and Touch ID HIG) and consult the Local Authentication documentation for the latest API updates. Start implementing today and give your users the secure, effortless authentication they expect.