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

U2F Php Laravel Package

spomky-labs/u2f-php

spomky-labs/u2f-php is a PHP library for implementing U2F (FIDO Universal 2nd Factor) authentication. It helps register and verify hardware security keys on the server, adding strong two-factor login protection to PHP applications and integrating with the WebAuthn ecosystem.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is archived — treat it as a legacy dependency for existing codebases only. For new work, use web-auth/webauthn-lib (the successor from the same maintainer) which covers U2F/FIDO2/WebAuthn in one library.

If you're inheriting a project that already uses it:

composer require spomky-labs/u2f-php

The package ships no Laravel service provider. You wire three things yourself:

  1. A config value for the App ID — the exact HTTPS origin the browser will present as the facet, e.g. https://app.example.com. Put it in config/u2f.php and env('U2F_APP_ID').
  2. A persistence layer for registered keys (one row per hardware token: key handle, public key, certificate, signature counter, user id, optional label).
  3. A thin service class wrapping U2f\U2FServer so controllers stay testable.

First use case to implement: registering a security key on an already-authenticated user. That single flow exercises every part of the package (challenge generation, JSON round-trip to the browser, response verification, persistence) and is the prerequisite for login.

// config/u2f.php
return ['app_id' => env('U2F_APP_ID')]; // must be the full origin, https in prod
// app/Services/U2fService.php
namespace App\Services;

use U2f\U2FServer;

class U2fService
{
    public function __construct(private string $appId) {}

    public function registrationRequest(array $registrations): object
    {
        return U2FServer::makeRegistrationRequest($this->appId, $registrations);
    }
}

Implementation Patterns

Registration flow

// GET /u2f/register — hand the browser its challenge
public function registerChallenge(U2fService $u2f)
{
    $existing = auth()->user()->u2fKeys->map(fn ($k) => $k->toRegistration())->all();
    $request  = $u2f->registrationRequest($existing);

    session(['u2f.register' => $request]); // the whole object, not the JSON

    return response()->json($request);
}
// POST /u2f/register — verify and persist
public function registerStore(Request $request, U2fService $u2f)
{
    $challenge = session()->pull('u2f.register'); // pull: one-shot, prevents replay
    abort_unless($challenge, 419);

    $response = U2FServer::checkRegistration($request->all(), $challenge);
    $reg      = $response->getRegistration();

    auth()->user()->u2fKeys()->create([
        'key_handle'  => $reg->getKeyHandle(),
        'public_key'  => $reg->getPublicKey(),
        'certificate' => $reg->getCertificate(),
        'counter'     => $reg->getCounter(),
        'label'       => $request->input('label'),
    ]);

    return response()->json(['ok' => true]);
}

Authentication flow

Mirror the same two-step shape. makeSignRequest returns an array of SignRequest objects (one per registered key), which is exactly what u2f-api.js expects:

$signRequests = U2FServer::makeSignRequest(config('u2f.app_id'), $registrations);
session(['u2f.sign' => $signRequests]);
return response()->json(['signRequests' => $signRequests]);

On the callback, checkSign($post, $signRequests) locates the matching request by key handle and verifies the signature. Always update the stored counter from the returned registration — a counter that fails to increase on the next login is your only real signal that a key has been cloned.

Storing challenges

Keep the challenge object in the session (or a short-lived u2f_challenges table for stateless/API clients) with a TTL of ~5 minutes. Never round-trip it through the client as anything other than the object the library generated — clients must not be able to mutate the challenge or app ID.

Front-end glue

You need u2f-api.js (the Google reference polyfill) in the browser; this package is server-side only. Typical Blade + JS:

u2f.register(appId, registerRequests, [], (data) => {
    if (data.errorCode) return showError(data.errorCode);
    axios.post('/u2f/register', data);
});

Store the registrationRequest JSON in a data attribute rather than inlining it into a <script>, so CSP nonces stay simple.

Integration tips

  • Cache the user's registration set per request; makeSignRequest is cheap but eager-loading avoids N+1 when listing keys.
  • Put U2F routes behind auth for registration and behind a rate-limited guest route for login (throttle:10,1 — hardware key guessing is slow but not free).
  • Model class with a toRegistration() cast helper keeps the domain layer free of U2f\ imports except in one place.

Gotchas and Tips

It is archived and deprecated. U2F is a dead protocol — most browsers now route U2F keys through WebAuthn rather than exposing u2f-api.js. Budget a migration to web-auth/webauthn-lib; the data model (key handle, public key, counter) mostly carries over, but you'll want to store credential IDs and transports too.

App ID mismatch is the #1 failure. The configured app_id must be a prefix of the real page origin, over HTTPS, and must not change between registration and authentication. Moving from example.com to app.example.com invalidates every registered key. Use a separate App ID per environment and expect dev keys registered on localhost to be useless in staging.

HTTPS is mandatory outside localhost. There is no bypass flag. Chrome will silently refuse to talk to the key if the origin is http.

Challenges are one-shot. Always session()->pull(), never session()->get(). If you leave the challenge in the session, a replayed response verifies twice.

Errors are exception-based and typed. checkRegistration/checkSign throw on bad signatures, unknown key handles, and counter regressions. Catch specifically and map to distinct UI messages — "unknown key" (user registered elsewhere) reads very differently to "counter error" (treat as compromised; revoke the key). Don't swallow these into a generic 422.

Counters can be zero. Some older/cheap keys report 0 on every assertion and never increment. Treat new < old as an error but new === old === 0 as acceptable, or you'll lock out a fraction of users.

The library touches PSR-7/HTTP foundation types. Older versions returned objects tied to Symfony HTTP classes. If you're passing raw Laravel request arrays in, unwrap with $request->all() and check the version's expected shape — a ParameterBag vs array mismatch produces confusing "missing parameter" errors.

Testing. No virtual authenticator ships with the package. For CI, either stub U2FServer behind your service class and assert the flow (challenge stored, response persisted, counter updated), or drive a real Playwright/Chrome virtual authenticator against a seeded credential. Unit-testing the crypto path itself is not worth the effort.

Extension points are thin. There's no repository interface to bind; persistence is entirely yours. If you want a pluggable key store, define your own U2fKeyRepository contract and inject it into your service wrapper — don't try to hook into the library internals.

Debugging tip: when a registration fails with a generic error, dump the clientData object returned by the browser and confirm its origin and challenge before blaming the library. Nine times out of ten the challenge in the session and the challenge in clientData differ because the user opened a second tab.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle