Skip to content

fix: recover from late keystore auth token delivery when signing - #93

Merged
sbaiahmed1 merged 2 commits into
mainfrom
fix/keystore-auth-token-delivery
Aug 2, 2026
Merged

fix: recover from late keystore auth token delivery when signing#93
sbaiahmed1 merged 2 commits into
mainfrom
fix/keystore-auth-token-delivery

Conversation

@sbaiahmed1

@sbaiahmed1 sbaiahmed1 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the KEY_USER_NOT_AUTHENTICATED (Keystore -26) failure in signWithOptions / verifyKeySignature reported in #91, where signing fails after a successful biometric authentication on certain OEM devices (reported on Motorola Edge 50 Neo / Moto G23 / Edge 40 Neo and Xiaomi 14 / 14 Ultra / Redmi Note 14 Pro+, Android 14–16).

Root cause

AOSP registers the Keystore auth token before invoking onAuthenticationSucceeded, but some OEM-customized biometric services deliver the token asynchronously. When the callback fires first, the immediate sign() fails with KEY_USER_NOT_AUTHENTICATED even though the user genuinely authenticated (see google/issuetracker#129937212). Worse, a failed sign() aborts the CryptoObject-bound Keystore operation, so the failure was previously unrecoverable.

Changes

  • Delay the first sign() attempt by 150 ms so a late-delivered auth token can reach Keystore while the CryptoObject-bound operation (and its challenge) is still alive. The delay is hidden by the prompt dismissal animation.
  • If the operation was already aborted with KEY_USER_NOT_AUTHENTICATED, retry once on a freshly initialized Signature after another 250 ms — this recovers on Keystore implementations that validate by recent authentication rather than strict per-operation challenge.
  • Surface a distinct KEY_USER_NOT_AUTHENTICATED error code (previously buried in the generic SIGNATURE_CREATION_FAILED) so apps can show a targeted retry prompt; documented in the README error-code table.

No changes to key generation or to the CryptoObject binding itself — the security model is unchanged.

Testing

  • :sbaiahmed1_react-native-biometrics:compileDebugKotlin passes (new arch codegen included).

  • Existing Jest suite passes (85/85).

  • Regression-verified end to end on the Android emulator (Pixel, API 35, fingerprint enrolled): createKeys('ec256') (auth-bound key) → Generate Signature → BiometricPrompt → fingerprint auth → signature produced, and validateSignature returned valid: true against the public key. Debug logcat shows the fix executing as designed:

    22:39:43.814  verifyKeySignature - Authentication succeeded, generating signature
    22:39:43.972  verifyKeySignature completed successfully (deviceCredential=false)
    

    158 ms apart — the 150 ms token-propagation delay plus the sign itself; the first attempt succeeds and the retry branch stays dormant on a compliant device, i.e. no behavior change on healthy hardware.

  • The KEY_USER_NOT_AUTHENTICATED failure itself is OEM-device-specific and does not reproduce on emulators; verification on affected hardware (Motorola/Xiaomi) is requested from the reporter in KEY_USER_NOT_AUTHENTICATED error when calling signWithOptions #91.

Refs #91

On some OEM builds (observed on Motorola and Xiaomi devices) the
keystore auth token from a successful BiometricPrompt authentication
is delivered asynchronously and may not have reached Keystore when
onAuthenticationSucceeded fires, so sign() fails with
KEY_USER_NOT_AUTHENTICATED (-26) despite genuine authentication
(https://issuetracker.google.com/issues/129937212).

Delay the first sign attempt slightly so late tokens can land while
the CryptoObject-bound operation is still alive, retry once on a
freshly initialized Signature if the operation was already aborted,
and surface a distinct KEY_USER_NOT_AUTHENTICATED error code when
both attempts fail.

Refs #91
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sbaiahmed1, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 57bdae21-af4a-4f86-8348-e64411f2159f

📥 Commits

Reviewing files that changed from the base of the PR and between 488b5a4 and 2411d7c.

📒 Files selected for processing (1)
  • android/src/main/java/com/sbaiahmed1/reactnativebiometrics/ReactNativeBiometricsSharedImpl.kt
📝 Walkthrough

Walkthrough

Android signing now delays Keystore operations, detects UserNotAuthenticatedException, retries once with a fresh signature, and returns distinct error codes. The README documents KEY_USER_NOT_AUTHENTICATED.

Changes

Android signature authentication retry

Layer / File(s) Summary
Timing and authentication error detection
android/src/main/java/com/sbaiahmed1/reactnativebiometrics/ReactNativeBiometricsSharedImpl.kt
The implementation defines propagation and retry delays. A helper detects user-not-authenticated failures through exception causes and messages.
Signature selection and retry flow
android/src/main/java/com/sbaiahmed1/reactnativebiometrics/ReactNativeBiometricsSharedImpl.kt, README.md
Device-credential and biometric signatures use separate selection paths. Signing performs a delayed first attempt and one delayed retry with a fresh signature when authentication has not propagated. The README documents the related error code and retry behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the fix for delayed Android Keystore authentication-token delivery during signing.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/keystore-auth-token-delivery

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@android/src/main/java/com/sbaiahmed1/reactnativebiometrics/ReactNativeBiometricsSharedImpl.kt`:
- Around line 1290-1302: Update the authentication classification around
authResult in the success path to use authResult.authenticationType rather than
authResult.cryptoObject?.signature for detecting device-credential
authentication. When it equals
BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, set fallbackUsed
to true and biometricStrength to "device_credential"; otherwise use
authenticatorResult.actualStrength, while preserving the existing signature
selection and signing flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b2e0cb07-c6fc-4455-b3ba-80647a05de83

📥 Commits

Reviewing files that changed from the base of the PR and between 4081fe5 and 488b5a4.

📒 Files selected for processing (2)
  • README.md
  • android/src/main/java/com/sbaiahmed1/reactnativebiometrics/ReactNativeBiometricsSharedImpl.kt

On API 30+ BiometricPrompt returns the bound CryptoObject even for
device-credential authentication, so a null crypto signature is not a
reliable indicator of how the user authenticated. Derive fallbackUsed
and the reported biometricStrength from authenticationType instead,
keeping signature selection based on the CryptoObject.
@sbaiahmed1

Copy link
Copy Markdown
Owner Author

Addressed in 2411d7c — device-credential classification now derives from authResult.authenticationType (AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL), while signature selection still prefers the CryptoObject-bound signature.

@sbaiahmed1
sbaiahmed1 merged commit a92c91e into main Aug 2, 2026
5 of 6 checks passed
@sbaiahmed1
sbaiahmed1 deleted the fix/keystore-auth-token-delivery branch August 2, 2026 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant