madwizard/webauthn
Server-side WebAuthn (FIDO2) implementation for PHP. Register and authenticate users with hardware security keys, platform authenticators, and passkeys, including attestation validation and framework-agnostic integration.
Architecture fit. madwizard/webauthn is a framework-agnostic WebAuthn Relying Party (RP) library: it implements the server half of the ceremony — challenge issuance, client data and authenticator data verification, signature validation against the stored COSE public key, attestation statement handling, and counter checks. It is not a Laravel package. There is no service provider, no config publish, no guard, no migration, and no Eloquent model. It expects you to supply a credential store and to hand it a request context (PSR-7-style / array inputs depending on the entry point). Practically, the architecture fit is "good engine, zero chassis." You will write the chassis.
The important boundary: this library owns verification, not user experience. You still need browser-side glue (navigator.credentials.create/get, ideally via a small JS module or @simplewebauthn/browser), a persistence layer, and a policy layer (when do we require a passkey vs. allow password fallback).
Integration feasibility. Feasible for one backend engineer in a 1–2 week spike, contingent on three adapters:
Illuminate\Http\Request into whatever the library consumes (PSR-7 via nyholm/psr7 if needed) and serialize the result to a JSON response.Cache::put with a short TTL keyed to a ceremony ID) works, but it must survive across the two round trips and be consumed atomically.CredentialStore-style interface over Eloquent. Budget for a non-trivial schema: credential ID (binary), user handle, COSE public key (binary/JSON), signature counter, AAGUID, transports, attestation type, backup-eligibility/backup-state flags, created/last-used timestamps.Technical risk. The dominant risk is not technical, it is supply chain and longevity: 61 stars and 0 dependents means effectively no downstream battle-testing. You are the integration test. A security-critical authentication primitive maintained by a very small contributor base is a standing audit and patching obligation. Secondary risks:
app.example.com vs example.com, or a staging domain) is a migration event, not a config change.web-auth/webauthn-lib is the de facto PHP standard with broader adoption and a Symfony bundle. Choosing the lower-adoption option needs a stated reason (licensing, API ergonomics, dependency weight) or it is just unjustified risk.Key questions.
none acceptable? Attestation is where most implementation cost and most library gaps live.Stack fit. Assume a Laravel 10/11 app on PHP 8.2+. Verify the library's PHP constraint against your runtime and its extension requirements (openssl, gmp/bcmath, dom, mbstring, json) — this is a common CI surprise. Bind the library's server object as a singleton in a service provider, parameterized by an config/webauthn.php holding RP name, RP ID, allowed origins, and timeouts. Do not let RP configuration live anywhere but config + env.
Isolation is the integration strategy. Define a narrow internal contract — e.g. App\Contracts\WebAuthnProvider with startRegistration, finishRegistration, startAuthentication, finishAuthentication — and put madwizard/webauthn behind it as the only implementation. This converts the library choice from an architectural commitment into a swappable dependency, which is the correct posture given the adoption signal. Contract-test the four methods against fixed vectors so a future swap to web-auth/webauthn-lib is a bounded, testable change rather than a rewrite.
Migration path. Additive, not disruptive. Ship a webauthn_credentials table and a webauthn_challenges (or cache-backed) store. Existing passwords stay. Registration is opt-in from account settings. Enrolment writes a credential row; authentication returns a user, which you then log in via Auth::login() with a fresh session ID. No changes to the existing users table beyond a nullable user handle if you don't reuse id.
Compatibility. Browser support for WebAuthn is effectively universal on modern desktop and mobile; the real compatibility surface is conditional UI (autofill passkeys), cross-device (hybrid/QR) flows, and older enterprise browsers or embedded webviews, which often block or silently fail the API. Detect and degrade to password + TOTP rather than hard-failing. Keep a second factor available for every user for at least two release cycles.
Sequencing.
Explicitly do not sequence "passwordless for everyone" first. The recovery and support burden, not the crypto, is what sinks these rollouts.
Maintenance. Ongoing cost is real but modest: dependency bumps on your normal cadence, plus an obligation to read WebAuthn-related advisories and library changelogs yourself — there is no vendor, no LTS, no SLA, and no security team behind this. Assume you may need to patch or fork. Pin the version, commit the lockfile, and require a second reviewer for any change touching the auth path. Budget roughly 1–2 engineer-days per quarter for upkeep, more in the first year.
Support. New support burden appears immediately: "my fingerprint doesn't work," "I got a new phone," "I cleared my browser." These are account-recovery tickets, not bugs, and they will arrive. You need a tier-1 script, a self-service path to add a second authenticator, and a break-glass recovery flow with audit logging and a delay window. Your helpdesk will also field users on browsers or corporate webviews where the API is unavailable — instrument client-side capability detection and log it so you can see the real failure population rather than guessing.
Scaling. Ceremony endpoints are cheap (signature verification is microseconds-to-milliseconds); there is no per-authentication external call, no vendor fee, and no lock-in. This is the strongest argument for WebAuthn over a hosted identity provider. The scaling concerns are mundane: challenge storage must be shared across app instances (Redis, not in-process memory), the credential table needs an index on credential ID, and ceremony endpoints need rate limiting because they are unauthenticated by definition during the first round trip.
Failure modes to design for.
TrustProxies must be correct or every ceremony fails validation. Test in staging with production-like headers.navigator.credentials rejects for many reasons; map errors to actionable messages, and never surface raw DOMException text.Ramp-up. One mid-level backend engineer needs ~2–3 days on the library API and ~1 week on WebAuthn ceremony semantics (client data JSON, authenticator data flags, attestation trust chains) to review integration code competently. Add time for whoever owns the frontend glue (~1 day). Institutional knowledge risk is concentrated in one or two people; document the flow and the RP ID rationale in an ADR.
Verdict. Technically suitable and architecturally clean, but the adoption signal (61 stars, 0 dependents, score 3.33) is the deciding factor. Adopt only behind a stable internal interface, only for a phased second-factor-first rollout, and only after a side-by-side spike against web-auth/webauthn-lib. If the spike shows no material advantage in API ergonomics or dependency footprint, take the mainstream option — for a security-critical auth primitive, ecosystem depth is a feature.
How can I help you explore Laravel packages today?