Biometric Authentication in iOS Apps: A Comprehensive How-To Guide

Fuad A. Waheed
13 min readOct 31, 2023

--

Image by macrovector on Freepik

In today’s digital age, security is paramount. Users expect their personal information to be kept safe. Biometric authentication is a powerful way to enhance the security of your iOS app. In this blog post, we’ll guide you through the process of implementing biometric authentication using Face ID and Touch ID in your iOS application.

We’ll create a Singleton class that handles this authentication and explain how to request the necessary permissions, usage of default images for all three types of biometric Authentication (i.e. touchID, faceID and the new opticID), where to save login information securely, and what to do if the user denies permission.

Step 1: Create a Singleton Class

The BiometricAuthManager Singleton class serves as a central component in your iOS application for handling biometric authentication, such as Face ID, Touch ID and Optic ID. Its primary purposes are as follows:

  1. Checking for Biometric Support: The class provides methods to determine if the user’s device supports biometric authentication. It detects whether features like Face ID or Touch ID are available on the device.
  2. Authenticating Users: It offers methods to initiate biometric authentication, enabling users to log in or perform sensitive actions securely. When authentication is successful, the class can provide access to protected information or allow the user to proceed with critical tasks.
  3. Storing and Retrieving Data Securely: The class can securely store sensitive user data, such as login credentials, using the Keychain or UserDefaults. These data storage methods ensure that the information remains protected and encrypted.
  4. Managing Biometric Settings: The class can also manage user preferences, such as whether they’ve enabled or disabled biometric authentication through a switch or settings within your app.
import LocalAuthentication
import UIKit

class BiometricAuthManager {
static let shared = BiometricAuthManager()

// UserDefaults key for the switch state
private let biometricSwitchKey = "biometricSwitchState"

private init() {}

// Function to set the state of the biometric switch
func setBiometricSwitchState(isOn: Bool) {
UserDefaults.standard.set(isOn, forKey: biometricSwitchKey)
}

// Function to get the state of the biometric switch
func isBiometricSwitchOn() -> Bool {
return UserDefaults.standard.bool(forKey: biometricSwitchKey)
}

func canUseBiometricAuthentication() -> Bool {
let context = LAContext()
var error: NSError?
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
}
func getBiometricType() -> LABiometryType {
let context = LAContext()
return context.biometryType
}
func authenticateWithBiometrics(completion: @escaping (Bool, Error?) -> Void) {
let context = LAContext()
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Authenticate using Face ID or Touch ID") { success, error in
DispatchQueue.main.async {
completion(success, error)
}
}
}
func showBiometricsSettingsAlert(_ controller: UIViewController) {
let alertController = UIAlertController(
title: "Enable Face ID/Touch ID",
message: "To use biometric authentication, you need to enable Face ID/Touch ID for this app in your device settings.",
preferredStyle: .alert
)
let settingsAction = UIAlertAction(title: "Go to Settings", style: .default) { _ in
if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)
}
}
alertController.addAction(settingsAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
controller.present(alertController, animated: true, completion: nil)
}
}

Step 2: Request Permission in Info.plist

Why It’s Important:

Biometric authentication, such as Face ID and Touch ID, involves the use of sensitive user data. Therefore, it’s important to request the necessary permissions and provide clear explanations of how and why your app intends to use biometric data. This aligns with user privacy requirements and builds trust with your app’s users.

Here’s how to request permission in the Info.plist file:

  1. Open Your App’s Info.plist: In Xcode, open your app’s Info.plist file. You can find this file in your project navigator under the “Supporting Files” group.
  2. Add a Privacy — Face ID Usage Description or Privacy. The key you need to add is one of the following:
  • Privacy — Face ID Usage Description (for Face ID): This key is used to request permission for Face ID usage.
  • Provide a Clear Description: In the value field for the chosen key, provide a clear and user-friendly description of why your app needs biometric authentication. Explain how it will enhance user experience or security. For example:

“We use Face ID/Touch ID to enhance the security of your app and protect your personal information.”

  • Code Snippet Example:
<key>NSFaceIDUsageDescription</key>
<string>We use Face ID/Touch ID to enhance the security of your app and protect your personal information.</string>

Step 3: Check for Biometric Support

In this step, you’ll learn how to use the BiometricAuthManager Singleton class to check if the user's device supports biometric authentication. We'll use the canUseBiometricAuthentication() method to perform this check and demonstrate how to enable a switch or button based on the result.

Why Checking for Biometric Support is Important:

Before attempting to use biometric authentication, it’s essential to verify whether the user’s device supports it. Not all devices have biometric sensors (e.g., Face ID, Touch ID and Optic ID), so it’s important to handle cases where biometric authentication is not available gracefully.

Here’s how to check for biometric support and enable a switch or button using the BiometricAuthManager class:

  1. Using the BiometricAuthManager: Make sure you've included the BiometricAuthManager Singleton class in your project, as explained in Step 1.
  2. Check for Biometric Support:
let biometricManager = BiometricAuthManager.shared
let canUseBiometrics = biometricManager.deviceSupportsBiometrics()

The deviceSupportsBiometrics() method checks whether the user's device supports biometric authentication, and it returns true if biometric authentication is available.

3. Enable a Switch or Button:
You can enable a switch or button based on the result of canUseBiometrics. For example, you can use the isEnabled property for a switch or button:

yourSwitch.isEnabled = canUseBiometrics

In this example, the switch will be enabled (user can turn it on) if the device supports biometric authentication, and it will be disabled if the device does not support it. You can use a similar approach with buttons or any other user interface element.

Here’s the complete code snippet for checking biometric support and enabling a switch:

let biometricManager = BiometricAuthManager.shared
let canUseBiometrics = biometricManager.deviceSupportsBiometrics()

// Enable a switch based on biometric support
yourSwitch.isEnabled = canUseBiometrics

Step 4: Set Biometrics Images

You can use BiometricAuthManager.shared.getBiometricType()to get the type of Biometric available and use default system images for respective type to show to user. Code snippet sample

func setUpBiometricImage() {
weak var weakSelf = self
if BiometricAuthManager.shared.canUseBiometricAuthentication() {
// Biometric authentication is available, you can enable a switch or a button to let the user turn it on.
switch BiometricAuthManager.shared.getBiometricType() {
case .faceID:
weakSelf?.biometricBtnImage.image = UIImage(systemName: "faceid")
case .touchID:
weakSelf?.biometricBtnImage.image = UIImage(systemName: "touchid")
case .opticID:
weakSelf?.biometricBtnImage.image = UIImage(systemName: "opticid")
default:
weakSelf?.biometricView.isHidden = true
}
} else {
weakSelf?.biometricView.isHidden = true
}
}

Step 5: Authenticate with Biometrics

Why Authenticating with Biometrics is Important:

Biometric authentication, such as Face ID or Touch ID, provides a secure and convenient way for users to access their apps. It’s crucial to handle the authentication process, and the Singleton class can simplify this task.

Here’s how to authenticate with biometrics using the BiometricAuthManager class and handle the success and failure scenarios:

  1. Using the BiometricAuthManager: Ensure that you have included the BiometricAuthManager Singleton class in your project, as explained in Step 1.
  2. Authentication with Biometrics:
let biometricManager = BiometricAuthManager.shared
biometricManager.authenticateWithBiometrics { success, error in
if success {
// Authentication successful
// You can proceed with protected actions or show sensitive data
print("Biometric authentication successful")
} else if let error = error {
// Handle authentication failure or errors
// For example, show an error message to the user
print("Biometric authentication failed with error: \(error.localizedDescription)")
} else {
// Biometric authentication was canceled or failed
// Handle the case where the user cancels or the authentication fails
print("Biometric authentication was canceled or failed")
}
}

The authenticateWithBiometrics method initiates the biometric authentication process. It takes a closure with two parameters: success (a Boolean indicating whether the authentication was successful) and error (an optional error object if authentication failed).

3. Handle Success and Failure Scenarios:

  • If success is true, the user's biometric authentication was successful. You can proceed with actions that require authentication or reveal sensitive data.
  • If error is not nil, authentication failed or an error occurred. You should handle this scenario. For example, you can show an error message to the user with details about what went wrong.
  • In the final else block, you can handle the case where the user cancels the biometric authentication or an unexpected error occurs.

Step 6: Store Login Information Securely

Importance of Securely Storing Login Information:

Storing login information securely is essential for several reasons:

  1. Data Security: User login credentials, such as passwords or tokens, are sensitive pieces of information. Storing them securely helps prevent unauthorized access or data breaches.
  2. User Trust: Safeguarding user data builds trust. When users feel that their information is protected, they are more likely to use your app and continue to interact with it.
  3. Legal and Regulatory Compliance: Depending on your app’s purpose and location, there may be legal or regulatory requirements for securing user data. Non-compliance can lead to legal consequences.

Using the iOS Keychain:

The iOS Keychain is a secure storage solution for sensitive data. It provides encryption, protection, and access control for credentials, encryption keys, and other secrets. It’s the recommended method for storing sensitive data in iOS applications.

Here’s a code snippet that demonstrates how to store and retrieve data from the iOS Keychain:

import Security
import UIKit

class KeychainManager {
static let shared = KeychainManager()

private init() {}

func storeLoginInfo(email: String, password: String) {
DispatchQueue.global().async {
let service = "BioAppService" // A service name for your app
let account = "BioAccount" // An account name
let emailKey = email.data(using: .utf8)
let passwordData = password.data(using: .utf8)
if let emailKey = emailKey, let passwordData = passwordData {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecValueData as String: passwordData,
kSecAttrGeneric as String: emailKey
]
SecItemDelete(query as CFDictionary) // Delete any existing data
let status = SecItemAdd(query as CFDictionary, nil)
if status == errSecSuccess {
print("Login information securely stored.")
} else {
print("Failed to store login information securely.")
}
}
}
}
func retrieveLoginInfo(completion: @escaping (String?, String?, Error?) -> Void) {
let service = "BioAppService"
let account = "BioAccount"

var query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]

// Use kSecReturnAttributes to retrieve attributes like email and username.
query[kSecReturnAttributes as String] = true

var dataTypeRef: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)

if status == errSecSuccess, let retrievedData = dataTypeRef as? [String: Any],
let passwordData = retrievedData[kSecValueData as String] as? Data,
let emailData = retrievedData[kSecAttrGeneric as String] as? Data,
let email = String(data: emailData, encoding: .utf8),
let password = String(data: passwordData, encoding: .utf8) {
// Successfully retrieved email and password
completion(email, password, nil)
} else if let error = SecCopyErrorMessageString(status, nil) as String? {
// Handle the error
completion(nil, nil, NSError(domain: "KeychainErrorDomain", code: Int(status), userInfo: [NSLocalizedDescriptionKey: error]))
} else {
// Handle the case where retrieval fails
completion(nil, nil, NSError(domain: "KeychainErrorDomain", code: Int(status), userInfo: nil))
}
}
}

This code snippet defines a KeychainManager class with two essential methods:

  1. storeInKeychain: This method securely stores data (in this case, a password) in the Keychain. It first deletes any existing data associated with the account, if present, and then adds the new data to the Keychain.
  2. retrieveFromKeychain: This method retrieves data from the Keychain based on the account name. If the data exists, it is returned as a string. If the data doesn't exist, or if there are any issues, it returns nil.

By using the Keychain, you can securely store login credentials and other sensitive information, providing a high level of protection against unauthorized access or data breaches.

Step 7: Handle Denied Permission

Why Handling Denied Permission is Important:

Handling denied biometric permission is crucial for user accessibility and ensuring a positive user experience. Not all users may be comfortable with or have access to biometric authentication methods like Face ID or Touch ID. Therefore, providing an alternative login method is essential to cater to a broader user base.

Here’s how to handle denied permission and provide an alternative login method:

  1. User Denies Biometric Permission:
    If the user denies permission to use biometrics, it’s important to gracefully handle this scenario. You can use a boolean value to keep track of whether biometric authentication is enabled or disabled based on the user’s preference.
    For example, in your BiometricAuthManager, you can have methods like setBiometricSwitchState(isOn: Bool) to store the user's preference and isBiometricSwitchOn() to check the current state.
  2. Fallback to a Password or PIN Login:
    If the user denies biometric permission, present an alternative login method, such as a PIN or password login screen. Ensure that the login screen is user-friendly and provides clear instructions for users to log in using the alternative method.
  3. Code Example for Fallback:
    In your view controller, you can use a conditional statement to determine whether to use biometric authentication or a fallback login method. For example:
let biometricManager = BiometricAuthManager.shared

if biometricManager.isBiometricSwitchOn() {
// User has enabled biometric authentication
// Attempt biometric authentication
biometricManager.authenticateWithBiometrics { success, error in
if success {
// Authentication successful, proceed with protected actions
print("Biometric authentication successful")
} else {
// Biometric authentication failed, present fallback login screen
print("Biometric authentication failed, present fallback login screen")
self.presentFallbackLoginScreen()
}
}
} else {
// Biometric authentication is disabled, present fallback login screen
// or show error alert
self.presentFallbackLoginScreen()
}

In this code, we check whether the user has enabled biometric authentication. If enabled, we attempt biometric authentication. If it fails, we present a fallback login screen. If biometric authentication is disabled, we directly present the fallback login screen.

4. Fallback Login Screen:

Design and implement a fallback login screen that allows users to enter a PIN or password. This screen should be user-friendly, with clear instructions, and should securely handle user credentials.

By providing a fallback login method, you ensure that users who deny biometric permission can still access your app using a secure alternative. This approach enhances user accessibility and ensures a positive experience for all users.

Step 7: Enhancing Security

In this step, we’ll discuss the importance of adding additional layers of security, such as session management and data encryption, to further protect user data. While biometric authentication provides a strong first layer of security, additional measures are necessary to safeguard sensitive data and ensure comprehensive security.

Importance of Enhancing Security:

  1. Data Protection: Biometric authentication primarily focuses on user identity verification during login. However, once a user gains access, it’s crucial to protect the data they interact with throughout their session.
  2. Preventing Unauthorized Access: Session management and data encryption help prevent unauthorized access to data during transit and at rest. This is vital in scenarios where users are interacting with sensitive information, like personal details or financial data.
  3. Compliance: Depending on the nature of your application and its users, regulatory requirements may demand additional layers of security. Implementing these security measures can help your app remain compliant with relevant standards.

Ways to Enhance Security:

  1. Session Management: Implement robust session management to control user access within a session. Ensure that users are logged out after a period of inactivity and provide mechanisms to log out manually. This reduces the risk of unauthorized access if a user steps away from their device.
  2. Data Encryption: Encrypt sensitive data both in transit and at rest. Use HTTPS for secure communication between your app and your server to prevent eavesdropping. For data at rest, consider using encryption techniques, like Apple’s Data Protection API, to protect data stored on the device.
  3. Secure Communication: Ensure that data transmitted between the client (your app) and your server is done securely. Avoid transmitting sensitive information in URLs and use secure communication protocols (HTTPS) to protect data during transmission.
  4. Authentication and Authorization: Besides biometric authentication, implement role-based access control to ensure that users can only access data and features they are authorized to use. Validate user permissions and roles on both the client and server sides.
  5. Data Sanitization: Sanitize and validate user inputs to prevent common security vulnerabilities like SQL injection, cross-site scripting (XSS), and cross-site request forgery (CSRF).
  6. Security Testing: Regularly test your application for security vulnerabilities, such as penetration testing and code reviews, to identify and address potential weaknesses.
  7. User Education: Educate your users about security best practices, such as the importance of choosing strong passwords and using biometric authentication. Inform them about your app’s security measures and how their data is protected.

By enhancing security through session management, data encryption, and other measures, you can ensure that your app provides a safe and trustworthy environment for users. Comprehensive security not only protects sensitive data but also builds trust and confidence among your user base.

SwiftUI

You can use this singleton class (alert function is only for UIKit in singleton class) in a SwiftUI view.

import SwiftUI

struct ContentView: View {
@State private var biometricEnabled = false

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

if biometricEnabled {
Button("Authenticate with Biometrics") {
BiometricAuthManager.shared.authenticateWithBiometrics { success, error in
if success {
// Biometric authentication was successful, proceed to login or reveal protected information.
} else {
if let error = error as? LAError {
// Handle the authentication error
switch error.code {
case .userCancel, .systemCancel:
// The user canceled the authentication
case .userFallback:
// The user chose to enter a password
default:
// Handle other authentication errors
print("Authentication failed: \(error.localizedDescription)")
}
}
}
}
}
} else {
Text("Biometric authentication is disabled. Enable it in your device settings.")
}

Toggle("Enable Biometrics", isOn: $biometricEnabled)
.onChange(of: biometricEnabled) { enabled in
if enabled {
enableBiometrics()
}
}
}
}

func enableBiometrics() {
if BiometricAuthManager.shared.canUseBiometricAuthentication() {
biometricEnabled = true
} else {
// Biometric authentication is not available; guide the user to the device settings.
showBiometricsSettingsAlert()
}
}

func showBiometricsSettingsAlert() {
// Display an alert to guide the user to the device settings.
}
}

BiometricsSettingsAlert struct:

import SwiftUI

struct BiometricSettingsAlert: View {
var body: some View {
Alert(
title: Text("Enable Face ID/Touch ID"),
message: Text("To use biometric authentication, you need to enable Face ID/Touch ID for this app in your device settings."),
primaryButton: .default(Text("Go to Settings"), action: {
if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)
}
}),
secondaryButton: .cancel()
)
}
}

Conclusion:

Biometric authentication is a powerful way to enhance the security and user experience of your iOS application. In this blog post, we’ve covered the entire process, from creating a Singleton class for biometric authentication to handling permission requests and securely storing login information. By following these steps, you can provide your users with a secure and convenient way to access their protected information.

Code Sample:

https://github.com/fuadwaheed/Biometric-Authentication-in-iOS-App/tree/main

Here is the git repository containing project. This project have

Singleton Classes

  1. ‘KeychainManager’
  2. ‘BiometricAuthManager’

Controllers

  1. ‘LoginVC’ in it you will see Authentication using Biometric Authentication and also storying user credentials in KeychainManager.
  2. ‘BalanceVC’ in it you will see balance being hidden then you will unhide balance using biometric authentication
  3. ‘SettingsVC’ in it you will see switch to turn on/off Biometric Authentication

Views

  1. ‘Main’ Storyboard contains all views
  2. ‘LaunchScreen’ Storyboard contains Launch View

--

--