For most iOS app projects, we usually need to implement FaceID or TouchID as an authentication feature for the application. This means that developers often have to redo the same implementation from one project to another. To minimize unnecessary rework, I decided to create a reusable SDK to support current and upcoming projects.
This post will demonstrate how we have built and published a biometric authentication SDK called NimbleLocalAuthentication on CocoaPods.
Architecture
Local Authentication Framework
TouchID/FaceID is a biometric authentication technology based on a framework called Local Authentication which authenticates users biometrically or with a passphrase.
Within the Local Authentication framework, we focused on the class LAContext
which implements a mechanism for evaluating authentication policies and access controls.
There are four available policies:
enum LAPolicy: Int {
case deviceOwnerAuthenticationWithBiometrics
case deviceOwnerAuthenticationWithWatch
case deviceOwnerAuthenticationWithBiometricsOrWatch
case deviceOwnerAuthentication
}
To assess whether authentication can proceed for a given policy, we used the method from the class LAContext
:
func canEvaluatePolicy(LAPolicy, error: NSErrorPointer) -> Bool
And to evaluate the specified policy, we used the following method:
func evaluatePolicy(LAPolicy, localizedReason: String, reply: (Bool, Error?) -> Void)
Keychain
When adding a biometric authentication feature into an application, there is always one additional feature that needs to be implemented: the ability to toggle the biometric authentication feature. However, this feature is not supported by default by the Local Authentication SDK.
I decided to add a dependency called KeychainAccess into our SDK to support the biometric authentication toggle feature.
KeychainAccess
is used to store the value biometricEnabled
internally and to access keychain objects solely in the NimbleLocalAuthentication
SDK.
Project Setup
Prerequisites
Create a pod project
We used the tool provided by the CocoaPods
gem to create a project.
pod lib create NimbleLocalAuthentication
After creating the project, we configured the SDK in the file NimbleLocalAuthentication.podspec
.
Pod::Spec.new do |s|
s.name = 'NimbleLocalAuthentication'
s.version = '1.0.0'
s.summary = 'A SDK support local authentication.'
s.homepage = 'https://github.com/nimblehq/local-authentication-ios'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = '@nimblehq'
s.source = { :git => 'https://github.com/nimblehq/local-authentication-ios.git', :tag => s.version.to_s }
s.ios.deployment_target = '11.0'
s.source_files = 'Sources/Classes/**/*'
s.swift_version = '5.0'
s.framework = 'LocalAuthentication'
s.dependency 'KeychainAccess'
end
When setting s.framework
and s.dependency
, the podspec file must use Local Authentication
and KeychainAccess
.
Implement the SDK
Implement the biometric authentication feature toggle
To implement the feature toggle, we created a class Keychain
to wrap the library KeychainAccess
and enum Keychain.Key
to support getting and storing the value biometricEnabled
.
final class Keychain { ... }
extension Keychain {
enum Key: String {
case biometricEnabled
}
}
We defined a protocol KeychainProtocol
to access the data which is stored in Keychain internally.
protocol KeychainProtocol: AnyObject {
subscript(bool key: Keychain.Key) -> Bool? { get set }
}
We implemented the protocol KeychainProtocol
with the class Keychain
.
extension Keychain: KeychainProtocol {
subscript(bool key: Key) -> Bool? {
// using KeychainAccesss library to get and set the data
get { ... }
set { ... }
}
}
With this implementation, we can easily access or set the value biometricEnabled
.
let keychain = Keychain()
// set the value
keychain[bool: .biometricEnabled] = true // or false
// access the value
keychain[bool: .biometricEnabled]
Implement the main class NimbleLocalAuthenticator
We defined a protocol named BiometryService
for SDK consumers to access the features of Nimble Local Authentication SDK.
import LocalAuthentication
public protocol BiometryService: AnyObject {
/// A boolean value stating whether the biometric feature is enabled.
var isEnabled: Bool { get set }
/// A boolean value stating whether the biometric feature is available.
var isAvailable: Bool { get }
/// Indicates the type of biometry supported by the device.
var supportedType: BiometryType { get }
/// Fallback button title.
var localizedFallbackTitle: String? { get set }
/// Cancel button title.
var localizedCancelTitle: String? { get set }
/// Allows setting the default localized authentication reason to show to the user.
var localizedReason: String { get set }
/// Allows running authentication in non-interactive mode.
var interactionNotAllowed: Bool { get set }
/// Authenticates the user when using the application.
///
/// @param completion Completeion block that is executed when authentication finishes.
/// success Completion parameter that is success if the authentication has been excuted successfully.
/// failure Completion parameter that is failed if the authentication has been authenticate failed.
/// contains error information about the authentication's failure.
///
/// @see BiometryError
func authenticate(completion: @escaping (BiometryResult) -> Void)
/// Invalidates the biometry service
func invalidate()
/// Credential provided by application
///
/// Sets a credential to this service
///
/// @param credential Credential to be used with subsequent calls. Setting this parameter to nil will remove
/// any existing credential of the specified type.
///
/// @param type CredentialType of the provided credential.
///
/// @return YES if the credential was set successfully, NO otherwise.
func setCredential(_ credential: Data?, type: CredentialType) -> Bool
/// Reveals if credential was set with this context.
///
/// @param type CredentialType of credential we are asking for.
///
/// @return YES on success, NO otherwise.
func isCredentialSet(_ type: CredentialType) -> Bool
}
In the SDK, we implemented the protocol BiometryService
with the class NimbleLocalAuthenticator and made it public.
public final class NimbleLocalAuthenticator { ... }
extension NimbleLocalAuthenticator: BiometryService { ... }
Publish The Pod
Release and tag the correct version
We released the version 1.0.0
(as defined in podspec
file), then tagged the version on Git:
git tag 1.0.0
git push origin 1.0.0
Verify the SDK
Before publishing the Nimble Local Authentication SDK to CocoaPods, we checked if the podspec
lints correctly:
pod lib lint NimbleLocalAuthentication.podspec
Publish the SDK
After the SDK passed validation, publishing the SDK on CocoaPods was done with the following command.
pod trunk push NimbleLocalAuthentication.podspec
Technically, the command published the spec to the CocoaPods’ Specs repository.
Upon success, the output was:
--------------------------------------------------------------------------------
🎉 Congrats
🚀 NimbleLocalAuthentication (1.0.0) successfully published
📅 February 5th, 14:33
🌎 https://cocoapods.org/pods/NimbleLocalAuthentication
👍 Tell your friends!
--------------------------------------------------------------------------------
Wrapping up
Using Local Authentication
and KeychainAccess
, the Nimble iOS team created an SDK called NimbleLocalAuthentication
which acts as a wrapper for Apple’s frameworks and provides additional features that will support iOS developers to easily implement TouchID/FaceID in any application 🎉.
If you have any questions, issues, or features, go to our repository. Issues and pull requests are welcome!