laravel / laravel/passkeys-server
Binary `getPasskeyUserHandle()` HMAC is corrupted by iOS Safari → `InvalidUserHandleException` on login
Nobody has claimed this yet.
- 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/passkeysv0.2.1web-auth/webauthn-lib5.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):
PasskeyLoginController::storeLaravel\Passkeys\Actions\VerifyPasskeyWebauthn\AuthenticatorAssertionResponseValidator::checkWebauthn\CeremonyStep\CheckUserHandle::processInvalidUserHandleException::create
Expected
Either:
- User handles that remain stable across authenticators/platforms that are lax about non-UTF-8
user.idbytes (e.g. ASCII-safe hex/base64url of the HMAC), and/or VerifyPasskeycatchesInvalidUserHandleExceptionand rethrows asInvalidPasskeyExceptionwith 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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