A security-focused PHP WebAuthn (FIDO2 / Passkeys) server library.
This is a maintained fork of lbuchs/WebAuthn by Report URI, forked at upstream v2.2.0. Goal: provide a small, lightweight, understandable library to protect logins with passkeys, security keys (Yubico, Solo), platform authenticators (Touch ID, Face ID, Windows Hello), etc. — with security fixes applied.
Upstream is effectively dormant. A pen test of Report URI's passkey integration surfaced several conformance issues; fixes were submitted as PRs to lbuchs/WebAuthn but have not been merged. This fork ships those fixes inline so consumers don't need to maintain patches of their own.
- Attestation removed entirely — only
fmt: 'none'with an emptyattStmtis accepted.getCreateArgs()always requestsattestation: 'none'from the browser, which is required by spec to strip the attestation statement regardless of which authenticator the user holds. All TPM / Packed / U2F / Android-Key / SafetyNet / Apple format handling has been deleted, along with the FIDO MDS plumbing and root-CA validation. The library is positioned for SaaS-style passkey auth where the RP only needs to know the user controls a credential bound to the RP — not which authenticator they used. - Tighter origin check — the previous regex-based RP-ID match treated the RP ID as a substring suffix (e.g. RP
example.comwould match hostevil-example.com). Now an exact match or true subdomain (RP ID preceded by a dot). - Cross-origin rejection —
processCreate/processGetnow reject ceremonies whereclientDataJSON.crossOrigin === true(WebAuthn Level 3 §7.1 Step 10, §7.2 Step 13). - Backup flag validation —
AuthenticatorDatanow rejects flag bytes where Backup State (BS) is set without Backup Eligible (BE), per spec. - Token Binding rejection —
clientDataJSON.tokenBinding.status === 'present'is rejected (WebAuthn Level 2 §7.1 Step 6, §7.2 Step 10), since this library does not implement Token Binding.
Each fix is a separate commit on main for easy auditing.
If you previously consumed lbuchs/WebAuthn (or an earlier build of this fork) and used attestation:
- The constructor no longer accepts an
$allowedFormatsargument. Drop the third positional argument:new WebAuthn($rpName, $rpId, $useBase64UrlEncoding = false). addRootCertificates(),addAndroidKeyHashes(), andqueryFidoMetaDataService()have been removed. Delete any calls to them.processCreate()no longer accepts$failIfRootMismatchor$requireCtsProfileMatch. Drop those arguments.- The
processCreate()result no longer carriesattestationFormat,certificate,certificateChain,certificateIssuer,certificateSubject, orrootValid. Remove any code that reads those fields. - Native Android app origins (
android:apk-key-hash:…) are no longer recognised; onlyhttpsorigins (andhttp://localhostfor development) are accepted. This affects only relying parties that ship a native Android app which calls the platform FIDO2 / Credential Manager API in-process — those calls produceandroid:apk-key-hash:origins. Browsers on Android (Chrome, Firefox, Edge, …) and any WebView-based flow still produce normalhttps://origins and work unchanged. - The
WebAuthnException::CERTIFICATE_NOT_TRUSTEDandWebAuthnException::ANDROID_NOT_TRUSTEDerror-code constants have been removed. Both were thrown only from attestation paths that no longer exist. Update anycatchblocks that branch on$e->getCode().
composer require report-uri/passkeys-phpThe library autoloads under PSR-4 as ReportUri\Passkeys\. The main entry point is ReportUri\Passkeys\WebAuthn (the class name is kept aligned with the W3C spec name).
use ReportUri\Passkeys\WebAuthn;
$server = new WebAuthn('My App', 'example.com');See _test/ for a simple working demo. The server.php + client.html pair exercises registration and login end-to-end.
JAVASCRIPT | SERVER
------------------------------------------------------------
REGISTRATION
window.fetch -----------------> getCreateArgs
|
navigator.credentials.create <-------------'
|
'-------------------------> processCreate
|
alert ok or fail <----------------'
------------------------------------------------------------
VALIDATION
window.fetch ------------------> getGetArgs
|
navigator.credentials.get <----------------'
|
'-------------------------> processGet
|
alert ok or fail <----------------'
A Client-side discoverable Credential Source is a public-key credential source whose private key is stored in the authenticator, client or client device. This requires a resident-credential-capable authenticator (FIDO2 hardware, not older U2F).
Passkeys allow sharing credentials stored on one device with other devices. From a server's perspective there is no difference to client-side discoverable credentials — the OS handles cross-device sync transparently.
In a typical server-side key management flow, the user enters their username (and maybe password). The server validates and returns a list of public-key identifiers for that user; the authenticator picks the first credential it issued and signs.
In a client-side flow, the user does not need to provide a username. The authenticator searches its own memory for a key bound to the relying party (domain). If a key is found, the authentication process proceeds as it would if the server had sent a list of identifiers.
When calling ReportUri\Passkeys\WebAuthn->getCreateArgs, set $requireResidentKey to true so the authenticator saves the registration in its memory.
When calling ReportUri\Passkeys\WebAuthn->getGetArgs, don't provide any $credentialIds — the authenticator will look up the IDs in its own memory and return the user ID as userHandle. Set the type of authenticator to hybrid (passkey scanned via QR code) and internal (passkey stored on the device itself).
The RP ID (domain) is saved on the authenticator. If an authenticator is lost it is theoretically possible to find the services it's used with and log in there.
Built-in passkeys that automatically sync to all of a user's devices: see passkeys.dev/device-support.
- Apple iOS 16+ / iPadOS 16+ / macOS Ventura+
- Android 9+
- Microsoft Windows 11 23H2+
- PHP >= 8.0 with OpenSSL and Multibyte String
- Browser with WebAuthn support
- PHP Sodium (or Sodium Compat) for Ed25519 support, or OpenSSL with native Ed25519 support (PHP ≥ 8.4)
The original library was written by Lukas Buchs under the MIT license. See NOTICE.md for full attribution.
MIT — same as upstream.