Use Face ID or Touch ID in your SwiftUI App
This Tutorial showcases how to add Biometric Authentication (Face ID, Touch ID) to your iOS App built in Swift and SwiftUI.
Posted by
Vladislav SmolyanoyRelated reading
Its very easy to do an overlay in SwiftUI. The problem is making app-wide overlays that go over every view. This article will show you how to do it using SceneDelegate.
One of the main reasons why you would want to use Face ID within your application would be to protect sensitive information of your users from unwanted attention.
Enabling it is straightforward, but requires a couple of steps. But first, let set out the goals of this tutorial. What do we want to achieve?
- We want to protect sensitive views with Biometric Auth and
- we want to protect performing sensitive operations with Biometric Auth
First, add a Usage Description for Biometric Authentication in your project's Info.plist
:
Then, let’s create a function that will perform the biometric authentication and return a boolean result, depending on if it’s successful or not.
For that, we’ll create a new class called BiometricAuth, that we will use to call authentication operation.
Here, we’ll create a function called authenticate()
:
import LocalAuthentication
public class BiometricAuth {
static public func authenticate() async -> Bool {
let context = LAContext()
var error: NSError?
// Check whether authentication is possible
if context.canEvaluatePolicy(
.deviceOwnerAuthentication, error: &error
) {
do {
// Return the result of the authentication
return try await context
.evaluatePolicy(
.deviceOwnerAuthentication,
localizedReason: "Authentication Required."
)
} catch {
// Handle your error here
print("Unhandled biometric auth err: \(error.localizedDescription ?? "Unknown")")
return false
}
} else {
// No Password or Biometrics to auth -> return true
return true
}
}
}
Congratulations! Now you can check for authentication by calling let success = await BiometricAuth.authenticate()
in your code!
In this function, we first check if the device is even capable of performing local authentication methods. That means it must have a password, a paired apple watch or a set up Face ID / Touch ID. If the user doesn’t have any security measures enabled on the device, we will simply return “true”, meaning “authenticated”.
If we are can evaluate authentication on the device, we await the evaluation and return its result.
In the next section, we will add some convenience wrappers around it.
Execute code after a successful or failed Face ID Authentication
For example, we often would want to call the method to authenticate and then execute A or B depending on the success of the authentication. To use our method in this scenario, we could use something like this:
// in BiometricAuth {}
static public func executeIfSuccessfulAuth(
_ onSuccessClosure: () -> Void,
otherwise onFailedClosure: (() -> Void)? = nil
) async {
guard await authenticate() else {
if let onFailedClosure {
onFailedClosure()
}
return
}
onSuccessClosure()
}
You can pass closures to this function that will execute either in case of a successful authentication or if the authentication fails, like this:
await BiometricAuth.executeIfSuccessfulAuth {
print("Successful Auth!")
} otherwise: {
print("Failed Auth!")
}
Note, that the `onFailedClosure` is optional, and you can just pass the `onSuccessClosure`
Protect SwiftUI Views with Biometric Authentication
Now, we will also create our own custom SwiftUI modifier to protect sensitive views from unwanted attention.
struct SenstivieViewModifier: ViewModifier {
@State private var hideView: Bool = true
@Environment(\.scenePhase) var scenePhase
// biometrics changes scenePhase to inactive. we need to address that when tracking changes in scenePhase
@State private var performingBiometricsRightNow: Bool = false
func tryToUnlock() {
Task {
performingBiometricsRightNow = true
await BiometricAuth.executeIfSuccessfulAuth {
withAnimation {
hideView = false
}
}
// We add this delay because when we perform a biometric auth operation, the
// state of the view changes to "not active". Even after it's completed, the view still
// takes around 2 secs to become "active" again.
try? await Task.sleep(for: .seconds(2.5))
performingBiometricsRightNow = false
}
}
func body(content: Content) -> some View {
content
.frame(maxWidth: .infinity,
maxHeight: .infinity)
.overlay {
if hideView {
VStack {
Image(systemName: "lock.fill")
.foregroundStyle(Color.secondary)
.font(
.system(
size: 75,
weight: .semibold
)
)
.padding(.bottom, 10)
Text("Private View")
.font(.largeTitle)
.bold()
Text("Open the App and authenticate to unlock.")
.font(.callout)
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
Button(
action: {
tryToUnlock()
},
label: {
Text("Unlock")
}
)
.padding(.top)
}
.frame(maxWidth: .infinity,
maxHeight: .infinity)
.background(.thickMaterial)
.ignoresSafeArea()
}
}
.task(id: scenePhase) {
if performingBiometricsRightNow { return }
withAnimation {
if scenePhase == .active && hideView {
tryToUnlock()
} else {
hideView = true
}
}
}
.onDisappear {
// we want to lock the view every time its not visible
hideView = true
}
}
}
This view modifier adds an overlay to the view it is attached to. This overlay is shown until the user performs a biometric authentication (automatically starts it when the view is active or by pressing the Unlock button). Once the view is inactive again, the overlay is shown again. This means that this view is also hidden in the app switcher. Cool!
You can attach it to any SwiftUI view like this:
ContentView()
.modifier(SenstivieViewModifier())
or create a wrapper view modifier:
extension View {
/// Protects a view with the requirement of having to
/// biometrically authenticate in order to see the view.
/// Also hides the view in the app switcher.
public func sensitiveView() -> some View {
modifier(SenstivieViewModifier())
}
}
that you can call like this
ContentView()
.sensitiveView()
Hope it helped you! If you have any questions you can always DM me on X (Twitter)
Having to manually build features like this and many others can be avoided by using SwiftyLaunch. You can simply select many features that you might need in your next app, like Auth, In App Purchases, Analytics and more, and SwiftyLaunch will generate a brand new app for you on the fly.
It also contains many useful convenience features, like the ones present in this tutorial. But instead of having to go through the trouble of writing them yourself, you can focus on working on what makes your app actually unique! Stop writing repetetive code and launch your next app on the App Store faster than ever by using SwiftyLaunch.
PS. SwiftyLaunch is currently on sale! Don’t miss out on this opportunity to save big and ship your apps faster than ever.