Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Biometric Auth Laravel Package

laranex/laravel-biometric-auth

laranex/laravel-biometric-auth adds WebAuthn-based biometric authentication to Laravel applications. Users can register and sign in with fingerprint, Face ID, or device passkeys, enabling secure passwordless login with straightforward Laravel integration. It streamlines credential setup and verifica...

View on GitHub
Deep Wiki
Context7

Getting Started

What this package actually is. It is the server half of a device-bound biometric login. It does not talk to Face ID, Touch ID, Android BiometricPrompt, or WebAuthn. Your client app performs the biometric prompt locally, unlocks a private key stored in the Secure Enclave / Android Keystore, signs a server-issued challenge, and posts the signature back. The package's job is narrow: store public keys against a user, mint a single-use challenge, and verify the signature that comes back. Everything else — routes, guards, token issuance, UX — is yours.

Because of that, the fastest way to evaluate it is to read the verifier, not the README.

Install and publish.

composer require laranex/laravel-biometric-auth
php artisan vendor:publish --provider="Laranex\BiometricAuth\BiometricAuthServiceProvider"
php artisan migrate

Pin the exact version in composer.json ("laranex/laravel-biometric-auth": "1.x" or a commit hash). With ~10 stars and a single maintainer, you want upgrades to be deliberate.

Where to look first, in this order:

  1. config/biometric-auth.php — challenge TTL, table/model names, accepted signature algorithms, any route registration flags.
  2. database/migrations/* — what a "device" row holds (public key format? algorithm column? last_used_at?) and what a "challenge" row holds. This tells you more about the design than the docs.
  3. The signature verification class itself — confirm exactly which encoding it expects for the signature and the public key (see Gotchas; this is where 90% of integration time goes).
  4. The service provider — whether it auto-registers routes or middleware you'll want to disable.

First use case: biometric login for an existing mobile app. Don't start with passwordless registration. Start with the additive flow:

  • User logs in normally (password or existing token).
  • Authenticated endpoint registers the current device: client generates a keypair, keeps the private key in the keystore, posts the public key.
  • Later, on the login screen, the client asks for a challenge, signs it, and exchanges the signature for a Sanctum/Passport token.

This keeps a working password path in place the whole time, so a signature-encoding bug is an inconvenience rather than a lockout.

Implementation Patterns

Enrollment behind existing auth. Registration must require a valid session — otherwise anyone can attach a key to any account. Bind the device to auth()->user(), store a client-generated or server-returned device identifier, and return that identifier to the client for storage alongside the private key. Cap devices per user (e.g. 5) and expose a revocation endpoint; a device list in account settings is table stakes for anything auth-related.

Challenge → sign → verify, with the challenge as the anti-replay primitive.

// 1. POST /auth/biometric/challenge  { device_id }
//    -> package issues a challenge row, returns the opaque challenge string
//    -> short TTL (30–60s), bound to the device

// 2. Client: biometricPrompt() -> sign(challenge) with keystore key

// 3. POST /auth/biometric/verify  { device_id, challenge, signature }
//    -> package verifies; you issue the token
$user = $device->user;
return ['token' => $user->createToken('biometric', ['*'])->plainTextToken];

The critical property is that verification consumes the challenge. Confirm in the source that a consumed or expired challenge cannot be replayed (deleted, or a consumed_at column checked atomically), and add a unique index on the challenge identifier in a follow-up migration if it isn't there.

Prefer an explicit endpoint over a custom guard. A POST /auth/biometric/verify controller that returns a token is easier to reason about, rate-limit, log, and test than a stateful guard that inspects signature headers on every request. Use the guard pattern only if you need transparent re-auth on many routes.

Step-up authentication. The highest-value pattern for this package isn't login — it's re-verification before sensitive actions (change password, add payout account, large transfer). Reuse the same challenge/verify flow but act on the current session rather than minting a new one, and record a biometric_verified_at timestamp on the session or a short-TTL cache flag that the sensitive endpoint checks.

Sign the context, not just the nonce. If your client signs only the challenge, that signature is valid for any endpoint that accepts a challenge. Have the challenge string incorporate the intended action (e.g. login:..., stepup:transfer:...) or have the client sign challenge + action + amount, and verify the binding server-side. This is the difference between a login primitive and a confused-deputy vulnerability.

Testing. Generate a throwaway keypair in the test suite (sodium or OpenSSL), register the public key through the real endpoint, then sign the returned challenge in the test before hitting verify. Freeze time (Carbon::setTestNow) to exercise TTL expiry, and write explicit tests for: reused challenge, expired challenge, wrong device, tampered body, and revoked device. Those five cases are the whole security surface.

Audit and lifecycle. Fire your own event after successful verification (device id, IP, user agent) and keep a log — biometric logins are exactly the kind of thing support will need to reconstruct. On repeated verification failures for a device, surface a "re-enroll this device" path rather than silently failing forever.

Gotchas and Tips

This is not WebAuthn, and shouldn't be sold as passkeys. No attestation, no RP ID / origin binding, no authenticator data or client-data JSON, no user-verification semantics. That's fine for a native app you control, where the TLS channel and your own client establish trust. It is not a drop-in for browser passkeys — for that use laragear/webauthn or web-auth/webauthn-lib. If a product requirement says "passkeys," this package is the wrong tool and you'll discover it during a security review.

Encoding mismatches are the #1 time sink. Signature as base64 vs base64url vs hex; public key as PEM vs raw DER/SPKI base64. The client library and the package will disagree by default. Debug by verifying the signature outside the package first — a five-line OpenSSL or sodium snippet against the stored key — so you know whether the bug is yours or the library's. If it verifies standalone but not through the package, it's an encoding or payload-normalization difference.

Payload normalization. If the client signs a JSON string and the server re-serializes it, key order and whitespace will differ and verification fails nondeterministically. Sign the raw bytes of the challenge (a plain opaque string) rather than a JSON blob, or sign a canonical form you control on both sides.

Silent-accept failure mode. A verifier that returns truthy on an error path is catastrophic and easy to introduce. Add a test that asserts a garbage signature fails. If the package's verifier catches exceptions and returns false, good; if it swallows them and returns null, wrap it and cast explicitly.

Pin the version and read the diff before upgrading. Low star count, no security disclosure process, no guarantee of a CVE feed. Vendor-review the small verification class once, then treat upgrades as code review events. Have a fork plan.

Clock skew and TTL. Keep the challenge TTL tight (≤60s) and make sure the client requests a fresh challenge per attempt rather than caching one. If the package compares timestamps, allow a few seconds of drift or users on slow networks will see flaky failures.

Rate limiting is your job. Throttle both the challenge and verify endpoints (throttle:5,1 per device and per IP). Without it, the challenge endpoint is a free oracle and the verify endpoint is brute-forceable. Also return a generic failure for unknown and revoked devices — don't let the error message enumerate device IDs.

Biometric is not a recovery path. Users will fail the prompt (wet hands, mask, new finger), and devices get wiped, taking the keystore key with them. Always keep password reset as the fallback and make sure "remove device" is a self-service action. A biometric-only login with no recovery flow is a support-ticket generator and a lockout risk.

Check the users table key type. If your users table uses UUIDs or ULIDs, the published migration's foreign key will be wrong. Fix it before running migrate, not after — rolling back a biometric device table in production is unpleasant.

Disable bundled routes if they exist. If the service provider registers endpoints, you almost certainly want them off: you'll want your own middleware, rate limits, route names, and response shapes. Set the config flag (or remove the route registration) and own the controllers.

Extension points worth knowing: the device model (swap in your own for soft deletes and relationships), the challenge model (add consumed_at, custom TTL), and the user resolver (multi-guard apps where the device belongs to an admin, not a User). Everything else is small enough to override by writing your own thin controller on top of the verifier.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core