Repository navigation
Conversation
Passkey login never sent the PRF extension: preparePublicKeyOptions dropped `extensions`, so the authenticator returned no PRF output, the server rejected the assertion with 401 and the login page silently fell back to the password step. Registration did pass the extension, but with the base64url salt still as a string. WebAuthn requires a BufferSource; browsers' native authenticators reject or ignore it, so registration only worked with password managers that accept the JSON encoding. Decode the PRF salts to ArrayBuffers in both places. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
Passkey login never works: after the user picks their passkey, the login
page drops them on the password step.
Passkey login needs the PRF extension. Its output decrypts the stored IMAP
password (
passkey_login_completeinbackend/src/routes/mfa.rs). ButpreparePublicKeyOptionsinfrontend/src/app/page.tsxcopies only thechallenge, rpId, allowCredentials and userVerification from the server
options. It drops
extensions, so the authenticator never computes a PRFoutput.
extract_prf_outputreturnsNone, the server answers 401 withoutlogging anything, and
attemptPasskeysilently falls back to the passwordstep.
Registration (
prepareCreationOptionsinSecuritySettings.tsx) does passextensions, but the PRF salt is still the base64url string from theserver. WebAuthn requires a
BufferSourcethere. Native browserauthenticators and hardware keys reject or ignore it. Password managers
that also accept the JSON encoding (
parseCreationOptionsFromJSON), suchas Proton Pass, decode it, which is why registration seemed to work.
Change
A small
prepareExtensions()helper in each file decodes the PRF salts toArrayBuffers:prf.evalByCredential[credId].firstprf.eval.firstBecause those password managers already decoded the string salt the same
way, passkeys registered before this fix keep working; no re-enrollment is
needed.
Testing
Deployed on a self-hosted instance (0.2.4, Firefox on Linux, Proton Pass):
registered before the fix and with a newly registered one.
🤖 Generated with Claude Code