LocalAuthentication.authenticate called without biometricOnly enforcement
ID |
dart.auth.weak_biometric_authentication |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
high |
Remediation Effort |
medium |
Resource |
Auth |
Language |
Dart |
Description
LocalAuthentication.authenticate(localizedReason: …) is invoked without biometricOnly: true. The default AuthenticationOptions allows the device PIN / pattern / passcode as a fallback, which defeats biometric protection for high-value flows.
Rationale
package:local_auth is the standard biometric API for Flutter. By
default it falls back to the device PIN / pattern / passcode when
biometrics are unavailable, not enrolled, or rejected by the user.
For low-stakes flows ("unlock the app to view balance") that
fallback is acceptable — the same protections that secure the
device’s lock screen apply.
For high-value, intent-confirming operations — releasing a
payment, exporting recovery codes, deleting an account,
authorising a passkey enrollment — the user’s deliberate
biometric presentation is the signal of intent. Falling back to
the device passcode means anyone who knows the lock-screen
passcode (shoulder-surfing, an over-the-shoulder family member,
a coerced user) can authorise the action. Passing
biometricOnly: true removes the passcode fallback.
The rule narrows by the presence of localizedReason: (the
signature shape of local_auth) so it doesn’t fire on unrelated
.authenticate(…) calls in other packages.
The following code illustrates a vulnerable pattern detected by this rule:
class WeakBiometric {
Future<bool> auth1() async {
final local = LocalAuthentication();
// VULNERABLE: LocalAuthentication.authenticate called without biometricOnly enforcement
return await local.authenticate(
localizedReason: 'Please authenticate to continue',
);
}
}
Remediation
For operations that require positive biometric proof, set
biometricOnly: true explicitly. Handle the "no biometrics
enrolled" case at the application layer (offer to enroll, or
refuse the high-value action), rather than silently falling back
to a weaker authenticator.
// Vulnerable: allows passcode fallback for a payment
await localAuth.authenticate(
localizedReason: 'Authorize this payment',
);
// Fixed: biometric proof required
final ok = await localAuth.authenticate(
localizedReason: 'Authorize this payment',
options: const AuthenticationOptions(
biometricOnly: true,
stickyAuth: true,
),
);
if (!ok) return; // refuse the action
References
-
OWASP Top 10 2025 - A07 : Authentication Failures.
-
package:local_auth (pub.dev) —
AuthenticationOptions(biometricOnly: true)reference -
Talsec — OWASP Mobile Top 10 for Flutter M3 — Insecure authentication and authorization in Flutter
-
OWASP MASTG — Testing Local Authentication — Mobile testing guide for biometric / local auth flows