local_biometrics_manager 0.1.2
local_biometrics_manager: ^0.1.2 copied to clipboard
Biometric authentication for Flutter on Android and iOS: fingerprint, face and iris detection, device-credential fallback, custom prompts and a live status stream.
local_biometrics_manager #
Advanced biometric authentication for Flutter on Android and iOS.
Detect the biometrics a device supports (fingerprint, face, iris, voice), check enrolment and lockout state, run authentication with a customizable prompt and optional device-credential (PIN/pattern/passcode) fallback, and subscribe to a live stream of status changes.
| Feature | Android | iOS |
|---|---|---|
| Fingerprint | ✅ | ✅ (Touch ID) |
| Face | ✅ | ✅ (Face ID) |
| Iris | ✅ (device dependent) | ➖ |
| Voice | ➖ | ➖ |
| Device credential fallback | ✅ | ✅ (passcode) |
| Custom prompt colors / icon | ✅ | ➖ (system sheet) |
| Status stream | ✅ | ✅ |
On iOS the OS renders the Face ID / Touch ID sheet, so the Android-only theming fields (
BiometricUIConfig) are accepted but ignored.
Supported versions #
- Android: minSdk 21+ (uses AndroidX
BiometricPrompt, which falls back toFingerprintManageron older devices automatically). - iOS: 12.0+ (uses
LocalAuthentication).
Installation #
dependencies:
local_biometrics_manager: ^0.1.0
Android #
No code changes are required — the plugin ships its own manifest permissions
(USE_BIOMETRIC, and the legacy USE_FINGERPRINT for API ≤ 27). Your host
Activity must be a FlutterActivity (the default), which is a
FragmentActivity — this is required by BiometricPrompt.
iOS #
Add a Face ID usage description to ios/Runner/Info.plist:
<key>NSFaceIDUsageDescription</key>
<string>We use Face ID to verify it's really you.</string>
Usage #
import 'package:local_biometrics_manager/local_biometrics_manager.dart';
final manager = LocalBiometricsManager.instance;
// 1. Check what the device can do.
final status = await manager.getBiometricStatus();
if (!status.isAvailable) {
// No usable hardware, or nothing enrolled.
}
// 2. Authenticate.
final result = await manager.authenticate(
const BiometricRequest(
title: 'Verify your identity',
subtitle: 'Access your account',
description: 'Use your fingerprint or face to continue.',
allowDeviceFallback: true, // offer PIN/passcode if biometrics fail
requireConfirmation: true,
timeoutSeconds: 30,
uiConfig: BiometricUIConfig( // Android only
primaryColorArgb: 0xFF6200EE,
negativeButtonText: 'Cancel',
),
),
);
if (result.isSuccess) {
// Unlock.
} else {
switch (result.errorCode) {
case BiometricErrorCode.canceled: // user backed out
case BiometricErrorCode.lockout: // too many tries
case BiometricErrorCode.notEnrolled:
// …handle each case
}
}
Check a specific type #
final hasFace = await manager.isBiometricSupported(BiometricType.face);
Cancel an in-flight prompt #
await manager.cancelAuthentication();
Listen for status changes #
final sub = manager.onBiometricStatusChanged.listen((status) {
// React to enrolment / lockout / availability changes.
});
// ...
await sub.cancel();
Hardware presence vs. enrollment (important) #
Two different questions, two different fields — don't confuse them:
- "Does the device have the sensor?" →
availableTypes,hardwareInfo.has*Sensor, andisBiometricSupported(type). These report hardware only and aretrueeven if nothing is enrolled.availableTypesmeans the same thing on Android and iOS. - "Can I actually authenticate right now?" →
isAvailable/isEnrolled(and the enrollment-awarehardwareInfo.canAuthenticateStrong/canAuthenticateWeak). Gate your "Authenticate" button onisAvailable.
Platform notes:
- On Android the OS can tell you that a biometric is enrolled, but not which modality — you can't ask "is a face enrolled" separately from "a fingerprint".
- Many Android phones expose face only for the lock screen, not to apps, so
hasFaceSensor/facemay befalseeven though the phone face-unlocks. allowedTypesis advisory: neither platform lets an app force a single modality. ListingdeviceCredentials(orany) is honored — it permits the device PIN/passcode.
Error codes #
All failures report one of these stable string codes on
AuthenticationResult.errorCode (see BiometricErrorCode):
| Code | Meaning |
|---|---|
BIOMETRIC_UNAVAILABLE |
Hardware not available |
BIOMETRIC_NOT_ENROLLED |
No biometric enrolled |
BIOMETRIC_LOCKOUT |
Too many failed attempts |
BIOMETRIC_CANCELED |
User cancelled |
BIOMETRIC_TIMEOUT |
Attempt timed out |
BIOMETRIC_FAILED |
General failure |
Routine outcomes (e.g. the user cancelling) come back as a normal
AuthenticationResult with isSuccess == false — authenticate does not
throw for these, so you rarely need a try/catch.
Edge cases handled #
- No biometric hardware, or hardware present but nothing enrolled.
- User cancelling mid-authentication (via the negative button or system back).
- App backgrounding during authentication (the OS dismisses; you get a result).
- Temporary and permanent lockout.
- Configuration changes such as rotation (Android re-binds the activity).
- Wide OS ranges: Android API 21–34+, iOS 12–17+.
Security notes #
- Authentication uses the platform's own secure prompt
(
BiometricPrompt/LocalAuthentication), which evaluates against hardware-backed biometrics where available. No biometric data ever crosses the platform channel — only a pass/fail result. - Contexts are freshly created per attempt and invalidated on cancel/timeout.
- Keep any secret you unlock on the native side or in secure storage; do not hold it in Dart longer than necessary.
Testing #
flutter test
The bundled tests cover the Dart facade, model (de)serialisation and the method channel with a mocked platform.
License #
MIT — see LICENSE.