[PM-41332] [BWA-260] fix: Recover from missing biometric key on Authenticator unlock - #2926
Open
stefan-jevtic wants to merge 1 commit into
Open
Conversation
…lock When the Authenticator's biometric unlock preference is enabled but the biometric key is missing from the keychain, the unlock screen renders a "Use Face ID to Unlock" button that can never succeed. Tapping it reads the absent keychain item, which fails without presenting a biometric prompt, so the button appears completely unresponsive and the user is locked out of the app. The key is lost whenever the app is reinstalled or the device is restored from a backup, neither of which clears the stored preference. Users whose device never registered the app as a biometrics client have no Face ID toggle in the Settings app, so they cannot apply the known workaround of revoking that permission and are locked out permanently. Handle BiometricsServiceError.getAuthKeyFailed by clearing the stale preference and completing auth, mirroring how the Password Manager's VaultUnlockProcessor already recovers from this error. This leaves the app in the same state the Settings-app workaround produces, and lets the user re-enable biometric unlock from the app's own settings, which writes a fresh key. Adds VaultUnlockProcessorTests, which did not previously exist for the Authenticator. Fixes bitwarden#2732 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collaborator
|
Thank you for your contribution! We've added this to our internal Community PR board for review. Details on our contribution process can be found here: https://contributing.bitwarden.com/contributing/pull-requests/community-pr-process. |
stefan-jevtic
marked this pull request as ready for review
August 1, 2026 19:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎟️ Tracking
Fixes #2732 (BWA-260)
📔 Objective
The Authenticator app can end up permanently locked behind a "Use Face ID to Unlock" button that does nothing when tapped.
Cause
getBiometricUnlockStatus()reports.available(_, enabled: true)from two independent sources: the device's biometry status, and the user's stored preference. Those can disagree. The stored preference survives an app reinstall and an iCloud restore; the biometric key in the keychain does not.When the preference says enabled but the key is gone,
AppCoordinatorroutes to the unlock screen, andVaultUnlockProcessor.unlockWithBiometrics()callsgetUserAuthKey(), which throwsBiometricsServiceError.getAuthKeyFailed. That error was funnelled intoloadData(), which re-reads the same two unchanged sources and produces the same enabled status. Nothing in the state changes and no error surfaces.The keychain lookup fails on a missing item, so iOS never presents a biometric prompt — which is why the button reads as completely unresponsive rather than as a failed scan. The key cannot be recovered, so every subsequent tap and every relaunch repeats the same dead end.
The reported workaround — revoking the app's biometrics permission in the Settings app — works because it flips the device half of the status to unavailable, so
AppCoordinatorskips the unlock screen entirely. It depends on iOS showing a Face ID toggle for the app, and iOS only shows that toggle once the app has evaluated a biometric policy at least once. A user who reinstalled before ever granting the permission has no toggle and no way back in (#2732 comment).Fix
Handle
BiometricsServiceError.getAuthKeyFailedexplicitly: log it, clear the stale preference viasetBiometricUnlockKey(authKey: nil), and complete auth.This mirrors the recovery
BitwardenShared'sVaultUnlockProcessoralready performs for the same error — the Authenticator's copy of that processor was simply missing the case. The end state is identical to what the Settings-app workaround produces today, so it grants no access the user cannot already obtain, and it reaches the users who currently have no workaround at all. Re-enabling biometric unlock from the app's own settings then goes throughevaluateBiometricPolicy(), which requests the permission and writes a fresh key, so the toggle recovers properly instead of resurrecting the broken state.Other error cases are unchanged: cancellation is still a no-op, and recoverable failures still fall through to
loadData()so a retry stays possible.Tests
Adds
VaultUnlockProcessorTests, which did not previously exist for the Authenticator. It covers the lockout regression, the successful-unlock path, cancellation, a recoverable error, and the case where clearing the preference itself fails.📸 Screenshots
Not applicable — no UI changes. The existing unlock screen is unchanged; it is now reached only when biometric unlock can actually succeed.