Skip to content

Fix passkey logins failing on iOS Safari due to binary user handles - #42

Open
alireza-aminzadeh wants to merge 1 commit into
laravel:mainfrom
alireza-aminzadeh:ascii-user-handles
Open

alireza-aminzadeh wants to merge 1 commit into
laravel:mainfrom
alireza-aminzadeh:ascii-user-handles

Conversation

@alireza-aminzadeh

Copy link
Copy Markdown

Fixes #41

getPasskeyUserHandle() returns raw binary HMAC bytes as the WebAuthn user.id. The sample handle in the report decodes to bytes full of EF BF BD (U+FFFD) — binary data that went through a lossy UTF-8 decode/encode, where bytes below 0x80 survive and everything else is replaced. iOS Safari does that to the user handle somewhere between registration and the assertion, so the returned handle can never match the stored one again and CheckUserHandle throws an unhandled InvalidUserHandleException, which surfaces as a 500.

Two changes:

  • getPasskeyUserHandle() now returns the hex form of the same HMAC (dropped binary: true). 64 ASCII characters, still within WebAuthn's 64 byte limit for user.id, and it survives any UTF-8 round trip.
  • VerifyPasskey catches InvalidUserHandleException and rethrows it as an InvalidPasskeyException. Credentials that already registered a binary handle from an affected device are unrecoverable server-side — the authenticator holds the corrupted user.id — so those logins now fail with a 422 telling the user to re-register instead of a 500.

The derivation change does not affect existing credentials: verification compares the assertion against the handle stored on the credential record, never a recomputed one (the same reason the existing secret-rotation test passes). New tests cover a legacy credential with a raw binary handle still verifying when the authenticator returns it intact, and a regression test that corrupts the handle the same way iOS does (UTF-8 decode with U+FFFD substitution) and asserts the login now fails with the validation error.

One side effect worth noting: a user who registers an additional passkey after this change gets a hex handle on the new credential while older credentials keep their binary one. Each credential is validated against its own stored handle, so both keep working.

composer test (Pint, PHPStan, Pest — 79 tests, 182 assertions) passes on PHP 8.5.

iOS Safari runs the raw HMAC user handle through a lossy UTF-8 decode,
so the handle returned with the assertion no longer matches the stored
one and login fails with an unhandled InvalidUserHandleException.

Derive the default handle as hex, which stays ASCII within the WebAuthn
64 byte limit, and rethrow handle mismatches as InvalidPasskeyException
so already-corrupted credentials fail with a validation error prompting
re-registration instead of a 500.

Existing credentials keep working because verification compares the
handle stored on the credential, never a recomputed one.
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.

Binary getPasskeyUserHandle() HMAC is corrupted by iOS Safari → InvalidUserHandleException on login

1 participant