Build a custom authentication flow using biometric sign-in
Biometric sign-in lets returning users sign in with their device's biometrics. After a normal sign-in or sign-up, your app can enroll the current installation as a trusted device. When the session expires, the user can sign in again with biometrics instead of their original sign-in method.
This is different from using biometrics only to unlock local app content. Biometric trusted-device sign-in authenticates with Clerk and creates a Clerk session. It is also different from a passkey: trusted-device credentials are scoped to the native app installation and don't use WebAuthn.
During enrollment, the SDK generates a device-bound key pair and registers the public key with Clerk. The private key never leaves the device. To sign in, the device signs a one-time Clerk challenge after the user approves the system authentication prompt. Clerk verifies the signature before creating a session.
Enable biometric sign-in
- In the Clerk Dashboard, navigate to the Native applications page and enable the Native API. This is required to integrate Clerk in your native application or browser extension.
- Navigate to the User & Authentication page and select the Biometric tab.
- Enable Sign-in with mobile biometrics.
If you use Clerk's prebuilt authentication views, you can also enable Prompt after sign-in or Prompt after sign-up. The prebuilt views will then offer enrollment after authentication and display biometric sign-in when a signed-out user has an enrolled credential. For a custom flow, use the APIs demonstrated in this guide.
To support Face ID, add NSFaceIDUsageDescription to your app's Info.plist. The value must explain why your app uses Face ID. Touch ID doesn't require additional Info.plist configuration.
<key>NSFaceIDUsageDescription</key>
<string>Use Face ID to sign in to this app.</string>Enroll a trusted device
Enroll the current app installation only after the user completes a normal sign-in or sign-up and Clerk has a session with a status of active or pending.
The following example demonstrates how to enroll the current iOS app installation. The reason is displayed in the system authentication prompt.
import SwiftUI
import ClerkKit
struct EnrollTrustedDeviceView: View {
@Environment(Clerk.self) private var clerk
var body: some View {
Button("Enable biometric sign-in") {
Task { await enrollCurrentDevice() }
}
}
}
extension EnrollTrustedDeviceView {
func enrollCurrentDevice() async {
do {
try await clerk.trustedDevices.enroll(
reason: "Use Face ID or Touch ID to sign in."
)
} catch {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling.
dump(error)
}
}
}Sign a returning user in
When the user no longer has an active session, check whether a local trusted-device credential is available before displaying biometric sign-in. While signed out, this availability check only inspects local state and can't confirm that the credential still exists in Clerk. If the subsequent sign-in reports that the credential no longer exists, the SDK removes the stale local state.
The following example checks availability and signs the user in with the enrolled credential.
import SwiftUI
import ClerkKit
struct SignInWithBiometricsView: View {
@Environment(Clerk.self) private var clerk
@State private var isAvailable = false
var body: some View {
Group {
if isAvailable {
Button("Sign in with biometrics") {
Task { await signInWithBiometrics() }
}
}
}
.task { await refreshAvailability() }
}
}
extension SignInWithBiometricsView {
func refreshAvailability() async {
do {
let availability = try await clerk.trustedDevices.availability()
isAvailable = availability.isAvailable
} catch {
isAvailable = false
}
}
func signInWithBiometrics() async {
do {
let signIn = try await clerk.auth.signInWithTrustedDevice()
if signIn.status != .complete {
dump(signIn.status)
}
} catch {
// Keep another sign-in method available when biometric sign-in fails.
dump(error)
await refreshAvailability()
}
}
}Revoke a trusted device
While the user is signed in, list their active trusted devices and let them choose which credential to revoke. Revoking a credential prevents Clerk from accepting it again. When the matching credential belongs to the current app installation, the SDK also deletes its local private key and credential metadata.
The following example lists trusted devices and revokes the selected credential.
import SwiftUI
import ClerkKit
struct RevokeTrustedDeviceView: View {
@Environment(Clerk.self) private var clerk
@State private var devices: [TrustedDevice] = []
var body: some View {
List(devices) { device in
HStack {
Text(device.name ?? "Trusted device")
Spacer()
Button("Revoke", role: .destructive) {
Task { await revoke(device) }
}
}
}
.task { await loadTrustedDevices() }
}
}
extension RevokeTrustedDeviceView {
func loadTrustedDevices() async {
do {
devices = try await clerk.trustedDevices.list()
} catch {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling.
dump(error)
}
}
func revoke(_ device: TrustedDevice) async {
do {
try await clerk.trustedDevices.revoke(id: device.id)
devices.removeAll { $0.id == device.id }
} catch {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling.
dump(error)
}
}
}Feedback
Last updated on