Flutter biometric authentication lets you gate sensitive actions with Face ID, Touch ID, or Android Biometrics. This guide shows a clean setup with local_auth
, UX patterns, and secure fallbacks.
Audience: IntermediateTested on: Flutter 3.x, Dart 3.x, macOS 14, Android 14 / iOS 17
Quick start: install & wire the plugin
Add the official package and platform integrations.
# pubspec.yaml (excerpt)
dependencies:
local_auth: ^latest
# Optional granularity on Android (fingerprint, device credential behavior)
local_auth_android: ^latest
iOS setup
- Open
ios/Runner/Info.plist
and add:<key>NSFaceIDUsageDescription</key> <string>Authenticate to proceed securely.</string>
- Ensure a device with Face ID/Touch ID enrolled (simulators often can be toggled via Features ▶ Face ID).
Android setup
- Most modern setups work without extra permissions, but if needed add:
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
toandroid/app/src/main/AndroidManifest.xml
. - Make sure your app uses AndroidX (default in recent Flutter templates).
Minimal example (authenticate on demand)
// dart
import 'package:local_auth/local_auth.dart';
class BiometricGate {
final _auth = LocalAuthentication();
Future<bool> canUseBiometrics() async {
final isDeviceSupported = await _auth.isDeviceSupported();
final biometrics = await _auth.getAvailableBiometrics();
return isDeviceSupported && biometrics.isNotEmpty;
}
Future<bool> promptBiometric({
String reason = 'Authenticate to continue',
bool biometricOnly = true,
}) async {
try {
final ok = await _auth.authenticate(
localizedReason: reason,
options: AuthenticationOptions(
biometricOnly: biometricOnly, // true: no PIN fallback
stickyAuth: false, // true to keep auth across app switch
useErrorDialogs: true,
sensitiveTransaction: true, // hints stronger protection
),
);
return ok;
} catch (e) {
// Log and map to UX (e.g., show snackbar)
return false;
} finally {
await _auth.stopAuthentication(); // safety: cancel any pending session
}
}
}
UX patterns that work
- Gate actions, not app launch. Prefer protecting sensitive screens (payments, profile edits) instead of blocking the entire app.
- Explain the why. The localizedReason should describe the benefit (“Authorize transfer”).
- Offer a fallback. If biometrics are unavailable, allow device credential or in-app passcode depending on your threat model.
- One retry, then back off. Avoid infinite prompts; respect system lockouts.
Choosing the right fallback
Decide per use-case:
- biometricOnly = true: Highest friction if biometrics fail; good for optional features (e.g., “unlock notes”).
- biometricOnly = false: Allows device credential fallback (PIN/pattern). Better for must-proceed actions (e.g., app sign-in).
- Custom in-app PIN: Consistent cross-platform UX but you must store/secure it (e.g., passkey+biometric hybrid later).
Sample screen flow
// dart (snippet)
// 1) Check support -> 2) Prompt -> 3) Route on success/failure
final ok = await gate.canUseBiometrics()
? await gate.promptBiometric(reason: 'Authorize payment')
: false;
if (ok) {
// proceed
} else {
// show fallback sheet: device credential or app PIN
}
Security & pitfalls
- Don’t store secrets in Dart memory long-term. Use platform secure storage for tokens/keys.
- Avoid auto-prompt loops. Prompt once per entry; re-prompt on explicit user action.
- Respect lockouts. After multiple failures the OS may lock biometric sensors; surface a gentle message.
- Simulator quirks. Always validate on real devices; simulators can fake success.
- Accessibility. Provide an alternative path for users without biometrics.
Troubleshooting
authenticate() throws “NotAvailable”? Check enrolled biometrics, isDeviceSupported()
, and Info.plist permission string.
Instant failure on Android 12+? Ensure device has a secure lock screen set; verify USE_BIOMETRIC
and AndroidX.
App resumes and loses state? Consider stickyAuth: true
for flows that switch apps (e.g., bank SMS), but test thoroughly.
FAQ
Q. Can I know whether it was Face ID or Touch ID?
A. You can inspect available biometric types, but treat them uniformly for UX and security.
Q. Is biometric data accessible to my app?
A. No. The system only returns success/failure signals—biometric templates never leave the Secure Enclave/TEE.
Q. How do I test failures?
A. Use simulator/device options to “enroll/unenroll” or “match/unmatch,” and test lockout scenarios with repeated failures.
Conclusion
Start with local_auth
, gate sensitive actions, and pick a fallback that matches your risk profile. Keep prompts purposeful, handle lockouts gracefully, and secure downstream secrets to build trust.
https://pub.dev/packages/local_auth
https://docs.flutter.dev/cookbook/plugins/biometrics
https://developer.android.com/training/sign-in/biometric-auth
https://developer.apple.com/design/human-interface-guidelines/face-id
Updated: 2025-10-11
Comment