Implementing Biometric Authentication in Mobile Apps: Face ID and Fingerprint | SoniNow Blog

Limited TimeLearn More

biometricauthenticationface idfingerprintmobile security

Implementing Biometric Authentication in Mobile Apps: Face ID and Fingerprint

Published

2026-06-23

Read Time

5 mins

Implementing Biometric Authentication in Mobile Apps: Face ID and Fingerprint

Biometrics Are a UX Feature, Not a Security Silver Bullet

Touch ID launched in 2013. Twelve years later, biometric authentication is expected by users, not celebrated. Banking apps, password managers, health record portals, and even social media apps ship with biometric unlock as a default feature. The question is no longer whether to implement it — it is how to implement it correctly without compromising security or user experience.

Biometric authentication does not replace your backend authentication. It replaces the friction of re-entering a password or PIN for session unlock. The secure token or session key is stored in the device secure enclave and released only after biometric verification. This distinction is critical and often misunderstood.

iOS: LocalAuthentication Framework

Apple provides the LocalAuthentication framework for Face ID and Touch ID. The API is straightforward but has subtle requirements around context, policy evaluation, and error handling:

// Swift — biometric authentication with LAContext
import LocalAuthentication

func authenticateWithBiometrics(reason: String) async throws -> Bool {
    let context = LAContext()
    context.localizedFallbackTitle = "Enter Passcode"
    context.localizedCancelTitle = "Cancel"

    var error: NSError?

    // Check if biometric authentication is available on this device
    guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
        throw BiometricError.unavailable(error?.localizedDescription ?? "No biometrics available")
    }

    return try await withCheckedThrowingContinuation { continuation in
        context.evaluatePolicy(
            .deviceOwnerAuthenticationWithBiometrics,
            localizedReason: reason
        ) { success, evaluateError in
            if success {
                continuation.resume(returning: true)
            } else {
                let error = evaluateError ?? BiometricError.unknown
                continuation.resume(throwing: BiometricError.failed(error.localizedDescription))
            }
        }
    }
}

Key considerations:

  • The localizedFallbackTitle appears when Face ID fails (e.g., user looks away). Setting it to "Enter Passcode" provides a clear fallback path.
  • Always evaluate .deviceOwnerAuthenticationWithBiometrics rather than .deviceOwnerAuthentication. The latter includes the device passcode as an option, which defeats the purpose of biometric gating.
  • On iOS 16+, use context.evaluatedPolicyDomainState to detect if enrolled biometrics have changed (new face or fingerprint added), invalidating stored tokens.

Android: BiometricManager and BiometricPrompt

Android's biometric API has converged around BiometricPrompt after years of fragmentation across fingerprint readers, face unlock, and iris scanners. The API handles all modalities uniformly:

// Kotlin — BiometricPrompt with CryptoObject for secure operations
class BiometricAuthHandler(private val activity: FragmentActivity) {

    fun authenticate(
        title: String,
        subtitle: String,
        onSuccess: () -> Unit,
        onError: (String) -> Unit,
        onFailed: () -> Unit
    ) {
        val executor = ContextCompat.getMainExecutor(activity)

        val biometricPrompt = BiometricPrompt(
            activity,
            executor,
            object : BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    onSuccess()
                }

                override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                    if (errorCode != BiometricPrompt.ERROR_USER_CANCELED) {
                        onError(errString.toString())
                    }
                }

                override fun onAuthenticationFailed() {
                    onFailed() // Biometric not recognized
                }
            }
        )

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle(title)
            .setSubtitle(subtitle)
            .setNegativeButtonText("Use Pin")
            .build()

        biometricPrompt.authenticate(promptInfo)
    }
}

Android's BiometricPrompt.AuthenticationResult includes an optional CryptoObject. When you wrap your authentication in a CryptoObject, the biometric verification is cryptographically tied to the operation — a malicious app cannot replay the authentication.

// Binding biometric auth to a cryptographic operation
val cryptoObject = BiometricPrompt.CryptoObject(
    Cipher.getInstance("AES/GCM/NoPadding")
)
biometricPrompt.authenticate(promptInfo, cryptoObject)

Cross-Platform with React Native

For React Native apps, react-native-biometrics (maintained since 2023) provides a unified API across iOS and Android:

// React Native — unified biometric authentication
import ReactNativeBiometrics, { BiometryTypes } from 'react-native-biometrics';

const rnBiometrics = new ReactNativeBiometrics();

async function setupBiometrics() {
  const { available, biometryType } = await rnBiometrics.isSensorAvailable();

  if (!available) {
    return { available: false, type: null };
  }

  return {
    available: true,
    type: biometryType, // BiometryTypes.Biometrics, FaceID, TouchID
  };
}

async function unlockWithBiometrics() {
  try {
    const { success } = await rnBiometrics.simplePrompt({
      promptMessage: 'Authenticate to access your account',
      cancelButtonText: 'Use PIN',
    });

    if (success) {
      // Retrieve stored credential from secure storage
      const token = await Keychain.getGenericPassword();
      return token;
    }
    return null;
  } catch (error) {
    console.error('Biometric auth failed:', error);
    return null;
  }
}

Security Considerations

Biometric authentication is convenient, but it is not a replacement for strong server-side security:

  1. Always have a fallback. The user's face can change, fingers can get wet, and sensors can fail. Require a PIN or passcode as a backup. Never lock a user out of their data because biometrics are unavailable.

  2. Use the Secure Enclave (iOS) / TEE (Android). Store session tokens in the device keychain (iOS) or EncryptedSharedPreferences backed by Android Keystore. Never store raw credentials in user defaults or shared preferences.

  3. Handle biometric enrollment changes. If a user adds a new face or fingerprint, the previous cryptographic key should be invalidated. iOS provides evaluatedPolicyDomainState for this. Android automatically invalidates keys when new biometrics are enrolled.

// Swift — detect biometric enrollment changes
let currentDomainState = context.evaluatedPolicyDomainState
if currentDomainState != storedDomainState {
    // Biometrics changed — invalidate stored tokens
    invalidateSession()
    // Prompt user to re-authenticate with full credentials
}
  1. Rate limiting. Biometric attempts should be rate-limited server-side. An attacker cannot brute-force Face ID (iOS locks after 5 attempts), but your API should still enforce its own rate limits.

The Implementation Checklist

Before shipping biometric authentication:

  • [ ] Request permission with clear rationale (iOS NSFaceIDUsageDescription)
  • [ ] Test on simulator and physical device (Face ID simulates in Xcode)
  • [ ] Handle all error states: unavailable, locked out, not enrolled, canceled
  • [ ] Provide a non-biometric fallback on every screen
  • [ ] Invalidate tokens when biometric enrollment changes
  • [ ] Log authentication attempts for security auditing (without storing biometric data)

Biometric authentication, when implemented correctly, is the single highest-impact UX improvement for mobile security. It reduces friction while maintaining strong protection.

At [SoniNow], we integrate biometric authentication into every mobile app we build, following platform best practices and security auditing standards.

Learn about our mobile development services →

Need help implementing biometric authentication? Get in touch.