laravel / laravel/passkeys-server

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

Open
#41 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

help wanted
Dominant language
PHP
Stars
110
Forks
8
PR merge metrics
No merged PRs in 30d

Description

Laravel Package Version

0.2.1

Laravel Version

13.24.0

PHP Version

8.5

Database Driver & Version

No response

Description

Passkey login on Mobile Safari (iOS) can fail with an unhandled Webauthn\Exception\InvalidUserHandleException: Invalid user handle at POST /passkeys/login.

The passkey is found by credential_id, but CheckUserHandle rejects the assertion because response.userHandle does not match the stored credential userHandle.

Environment
  • laravel/passkeys v0.2.1
  • web-auth/webauthn-lib 5.3.x
  • Laravel Fortify passkeys feature enabled (stock setup, no custom getPasskeyUserHandle())
  • Client: @laravel/passkeys
  • Browser: Mobile Safari on iOS 18.7 (iPhone)
What happens

PasskeyAuthenticatable::getPasskeyUserHandle() returns raw binary HMAC-SHA256 (binary: true). That value is stored on the credential at registration.

On login, iOS Safari returns a userHandle whose decoded bytes are full of UTF-8 replacement characters (U+FFFD / EF BF BD). Example from production (base64url):

77-977-977-977-9XGFVCRrvv73vv71xG--_vSDvv73vv71MZu-_ve-_vVbvv70_Ge-_vVbvv70FK--_vQ

That pattern matches binary data that was UTF-8-decoded with replacement and then re-encoded. @simplewebauthn/browser serializes ArrayBuffers with String.fromCharCode + btoa, so it does not introduce U+FFFD itself, Safari appears to already return a corrupted userHandle ArrayBuffer.

Stack (abbreviated):

  1. PasskeyLoginController::store
  2. Laravel\Passkeys\Actions\VerifyPasskey
  3. Webauthn\AuthenticatorAssertionResponseValidator::check
  4. Webauthn\CeremonyStep\CheckUserHandle::process
  5. InvalidUserHandleException::create
Expected

Either:

  1. User handles that remain stable across authenticators/platforms that are lax about non-UTF-8 user.id bytes (e.g. ASCII-safe hex/base64url of the HMAC), and/or
  2. VerifyPasskey catches InvalidUserHandleException and rethrows as InvalidPasskeyException with a user-facing message instead of a 500.
Actual

Unhandled exception / 500. Affected passkeys cannot sign in until removed and
re-registered.

Steps To Reproduce

N/A

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with PasskeyAuthenticatable::getPasskeyUserHandle() to trace how the binary HMAC user handle is generated and stored, then inspect VerifyPasskey and the InvalidUserHandleException path. Confirm the chosen compatibility and error-handling behavior against the Mobile Safari failure, and consider how registration and login should remain consistent before adding regression coverage.

Written by the indexing model from the issue text.

Assessment

Tech stack
laravel, php
Domain
authentication, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.