PHPackages                             padosoft/laravel-rebel-bridge-passkeys - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. padosoft/laravel-rebel-bridge-passkeys

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

padosoft/laravel-rebel-bridge-passkeys
======================================

WebAuthn passkey step-up driver for Laravel Rebel: bridges spatie/laravel-passkeys into Rebel's step-up registry, issuing phishing-resistant AAL3 challenges.

v0.1.0(1mo ago)02MITPHPPHP ^8.3CI passing

Since Jun 4Pushed 1mo agoCompare

[ Source](https://github.com/padosoft/laravel-rebel-bridge-passkeys)[ Packagist](https://packagist.org/packages/padosoft/laravel-rebel-bridge-passkeys)[ Docs](https://github.com/padosoft/laravel-rebel-bridge-passkeys)[ RSS](/packages/padosoft-laravel-rebel-bridge-passkeys/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (12)Versions (3)Used By (0)

laravel-rebel-bridge-passkeys
=============================

[](#laravel-rebel-bridge-passkeys)

> Official documentation:

[![Laravel Rebel Banner](resources/screenshoots/Laravel-Rebel-banner.png)](resources/screenshoots/Laravel-Rebel-banner.png)

**WebAuthn passkey step-up driver for [Laravel Rebel](https://github.com/padosoft/laravel-rebel-core).**

Bridges `spatie/laravel-passkeys` (or any WebAuthn library you prefer) into Rebel's step-up `DriverRegistry`, giving you a **phishing-resistant (AAL2)** step-up factor with one `composer require`and two lines of configuration.

[![CI](https://github.com/padosoft/laravel-rebel-bridge-passkeys/actions/workflows/ci.yml/badge.svg)](https://github.com/padosoft/laravel-rebel-bridge-passkeys/actions/workflows/ci.yml)[![PHPStan](https://camo.githubusercontent.com/14995ff65edea59395c224e37e4fc66f91c1e601c1a58311e3c6f38c4fe37feb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c2532306d61782d627269676874677265656e)](https://phpstan.org/)[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](LICENSE)

---

Glossary
--------

[](#glossary)

Before diving in, here are the terms you will encounter throughout this README.

TermWhat it means**Passkey**A cryptographic credential stored in a hardware-backed secure enclave (phone, laptop TPM, YubiKey). You use it with Face ID, fingerprint, or a PIN. No password involved.**WebAuthn / FIDO2**The W3C + FIDO Alliance standard that defines how browsers talk to authenticators. Passkeys are WebAuthn credentials.**Phishing-resistant**An assertion is cryptographically bound to the *exact* origin (domain + protocol) of the page that called `navigator.credentials.get()`. A phishing site on a different domain literally cannot capture and replay a valid assertion — the signature simply won't verify. OTP/TOTP/SMS are NOT phishing-resistant.**AAL2 (phishing-resistant)**NIST SP 800-63B Authenticator Assurance Level 2. This driver declares **AAL2 + phishing-resistant**: a passkey can't be phished, but most consumer passkeys are *synced* across a vendor cloud (iCloud Keychain, Google Password Manager) rather than hardware-bound, so strict NIST reserves **AAL3** for device-bound/security-key authenticators only. The honest default is AAL2-phishing-resistant; a deployment that mandates device-bound passkeys can raise it.**AMR**Authentication Methods Reference — a list of short strings (e.g. `['webauthn']`) that describe HOW the user authenticated. Used by compliance and audit systems.**Step-up**An in-session re-authentication challenge. The user is already logged in; before a sensitive action (e.g. changing payment method, approving a transfer) you ask them to confirm their identity again using a strong factor.**DriverRegistry**The Rebel step-up registry. Each package contributes its own driver (email-OTP, TOTP, passkey…). The Rebel core selects the right driver for a given purpose/assurance policy.**PasskeyChallenger**The seam (interface) in this bridge. It decouples the driver logic from any specific WebAuthn library.---

How a passkey step-up works (step by step)
------------------------------------------

[](#how-a-passkey-step-up-works-step-by-step)

```
Browser                        Your Laravel App                  Secure Enclave / Key
   |                                   |                                  |
   |--- 1. User clicks "Confirm" ----->|                                  |
   |                                   |--- 2. start() ----------------->|
   |                                   |    PasskeyChallenger.startChallenge()
   |                                   |    Generates a fresh random nonce (challenge)
   ||
   ||                                  |
   |                                   |--- 7. verify() ---------------->|
   |                                   |    PasskeyChallenger.verifyAssertion()
   |                                   |    - challenge matches nonce from step 2?
   |                                   |    - cryptographic signature valid?
   |                                   |    - origin == registered domain?
   |                                   |    => true or false
   |app->singleton(PasskeyChallenger::class, SpatiePasskeyChallenger::class);
}
```

### 3. Add HasPasskeys to your User model

[](#3-add-haspasskeys-to-your-user-model)

```
use Spatie\LaravelPasskeys\Models\Concerns\HasPasskeys;
use Spatie\LaravelPasskeys\Models\Passkeys\HasPasskeysTrait;

class User extends Authenticatable implements HasPasskeys
{
    use HasPasskeysTrait;

    // ...
}
```

### 4. Publish the config (optional)

[](#4-publish-the-config-optional)

```
php artisan vendor:publish --tag="rebel-bridge-passkeys-config"
```

---

Bringing your own WebAuthn library
----------------------------------

[](#bringing-your-own-webauthn-library)

Not using `spatie/laravel-passkeys`? No problem. Implement the `PasskeyChallenger` interface against your preferred library (e.g. `web-auth/webauthn-framework`) and bind it:

```
use App\WebAuthn\MyWebAuthnChallenger;
use Padosoft\Rebel\Bridge\Passkeys\Contracts\PasskeyChallenger;

$this->app->singleton(PasskeyChallenger::class, MyWebAuthnChallenger::class);
```

The interface you need to implement:

```
interface PasskeyChallenger
{
    // Does this user have at least one passkey registered?
    public function hasPasskey(Authenticatable $user): bool;

    // Issue a fresh single-use challenge. Return an opaque string (the nonce,
    // or serialised WebAuthn options JSON) that you can later pass to verifyAssertion().
    public function startChallenge(Authenticatable $user): string;

    // Verify the assertion (JSON from navigator.credentials.get()) against
    // the challenge issued by startChallenge(). Return true on full success.
    public function verifyAssertion(Authenticatable $user, string $assertion, string $challenge): bool;
}
```

---

Configuration
-------------

[](#configuration)

`config/rebel-bridge-passkeys.php`:

KeyEnvironment variableTypeDefaultDescription`drivers.passkeys``REBEL_PASSKEYS_DRIVER_PASSKEYS``bool``true`Enable the `'passkeys'` step-up driver. The driver is only registered when this is `true` AND a `PasskeyChallenger` is bound in the container. Set to `false` to disable the driver without unbinding the challenger.---

Usage examples
--------------

[](#usage-examples)

### Example 1 — Protect a route with a passkey step-up

[](#example-1--protect-a-route-with-a-passkey-step-up)

```
// routes/web.php
Route::post('/account/delete', [AccountController::class, 'destroy'])
    ->middleware(['auth', 'rebel.stepup:delete-account']);
```

```
// config/rebel-step-up.php
'purposes' => [
    'delete-account' => [
        'required_aal' => 'aal2',
        'require_phishing_resistant' => true,
        'drivers' => ['passkeys'],
    ],
],
```

### Example 2 — Manual step-up in a controller

[](#example-2--manual-step-up-in-a-controller)

```
use Padosoft\Rebel\StepUp\RebelStepUp;
use Padosoft\Rebel\StepUp\StepUpContext;
use Padosoft\Rebel\Core\Context\SecurityContext;

class PaymentController extends Controller
{
    public function confirm(Request $request, RebelStepUp $stepUp): JsonResponse
    {
        $context = new StepUpContext(
            subject: $request->user(),
            purpose: 'confirm-payment',
            security: SecurityContext::fromRequest($request, app(KeyedHasher::class)),
        );

        // Start the step-up: issues a WebAuthn challenge.
        $result = $stepUp->start($context, driver: 'passkeys');

        return response()->json([
            'challenge_reference' => $result->reference,
            'webauthn_options' => $result->options,
        ]);
    }

    public function verify(Request $request, RebelStepUp $stepUp): JsonResponse
    {
        $context = new StepUpContext(
            subject: $request->user(),
            purpose: 'confirm-payment',
            security: SecurityContext::fromRequest($request, app(KeyedHasher::class)),
        );

        $verified = $stepUp->verify(
            $context,
            input: $request->input('assertion'),      // JSON from navigator.credentials.get()
            reference: $request->input('reference'),  // reference from start()
        );

        if (! $verified) {
            return response()->json(['error' => 'Step-up failed'], 422);
        }

        // Proceed with the protected action.
        $this->processPayment($request);

        return response()->json(['status' => 'confirmed']);
    }
}
```

### Example 3 — Check if the driver is available before showing the UI

[](#example-3--check-if-the-driver-is-available-before-showing-the-ui)

```
use Padosoft\Rebel\StepUp\DriverRegistry;
use Padosoft\Rebel\StepUp\StepUpContext;

$driver = app(DriverRegistry::class)->get('passkeys');

if ($driver && $driver->isAvailableFor($context)) {
    // Show the "Use passkey" button.
}
```

### Example 4 — Testing with FakePasskeyChallenger

[](#example-4--testing-with-fakepasskeychallenger)

```
use Padosoft\Rebel\Bridge\Passkeys\Contracts\PasskeyChallenger;
use Padosoft\Rebel\Bridge\Passkeys\Testing\FakePasskeyChallenger;

// In your test boot or TestCase::defineEnvironment():
$app->singleton(PasskeyChallenger::class, fn () => new FakePasskeyChallenger(
    registered: true,           // user has a passkey
    expectedAssertion: 'valid', // the assertion string that will be accepted
    challenge: 'test-nonce',    // the challenge returned by startChallenge()
));

// All step-up driver logic works offline without any WebAuthn ceremony.
```

---

Audit events
------------

[](#audit-events)

Every step-up action is recorded to `rebel_auth_events` via the core `AuditLogger`. The following events are emitted so that the step-up funnel, compliance AMR breakdown, and audit explorer panels in the Rebel admin all light up:

Event typeWhen`channel``amr``aal``stepup.passkeys.started`A challenge was issued via `start()``passkey``['webauthn']``aal2``stepup.passkeys.verified`Assertion verified successfully`passkey``['webauthn']``aal2``stepup.passkeys.failed`Assertion rejected, missing reference, or any exception`passkey``['webauthn']``aal2`All events include `subjectType` and `subjectId` (the authenticated user's class and ID). The raw WebAuthn assertion and credential bytes are **never** included in any audit field.

---

Competitor card-battle table
----------------------------

[](#competitor-card-battle-table)

How does passkey-based phishing-resistant (AAL2) step-up compare to what other platforms offer?

FeatureLaravel Rebel (this package)Laravel Fortify (plain)ShopifyAuth0OktaPasskey / WebAuthn step-up✅ AAL2, phishing-resistant❌ No step-up system❌ No developer step-up API⚠️ Enterprise tier only⚠️ Paid addonPhishing-resistant by spec✅ Origin-bound by design❌❌✅ (if enabled)✅ (if enabled)AMR / AAL in audit trail✅ `['webauthn']`, AAL2❌ No standard audit❌⚠️ OIDC claims only⚠️ OIDC claims onlyBring your own WebAuthn lib✅ Any library via PasskeyChallenger❌❌❌❌Works without Fortify✅ Zero Fortify dependencyN/AN/AN/AN/AOffline tests (no browser)✅ FakePasskeyChallenger❌❌❌❌Step-up funnel analytics✅ started/verified/failed events❌❌⚠️ Paid dashboard⚠️ Paid dashboardOpen source / MIT✅✅ (no step-up)❌ Closed SaaS❌ Closed SaaS❌ Closed SaaS---

Vibe coding with batteries included
-----------------------------------

[](#vibe-coding-with-batteries-included)

This package ships everything an AI agent or a new contributor needs to keep working productively:

- **`CLAUDE.md`** — AI working guide: conventions, security rules, PHPStan recipes, Definition of Done.
- **`AGENTS.md`** — Operational contract: branching, PR flow, guardrails, release checklist.
- **`.claude/skills/rebel-package-dev/SKILL.md`** — Invocable skill for the dev loop (TDD, PHPStan-max fixes, telemetry rules).
- **`Testing\FakePasskeyChallenger`** — Deterministic test double: configure accept/reject, which users have a passkey, and which challenge is issued. Fully offline — no browser, no WebAuthn API call.

### Dev loop

[](#dev-loop)

```
composer test       # Pest tests (offline, uses FakePasskeyChallenger)
composer phpstan    # Static analysis, level max
composer pint       # Code style (Laravel preset)
```

### Quick start for contributors

[](#quick-start-for-contributors)

```
git clone https://github.com/padosoft/laravel-rebel-bridge-passkeys
cd laravel-rebel-bridge-passkeys
composer install
composer test
```

---

Package structure
-----------------

[](#package-structure)

```
src/
  Challengers/
    SpatiePasskeyChallenger.php   # Production adapter for spatie/laravel-passkeys
  Contracts/
    PasskeyChallenger.php         # The seam: swap the WebAuthn library here
  Drivers/
    PasskeysStepUpDriver.php      # Core driver (key:'passkeys', AAL2, phishing-resistant)
  Testing/
    FakePasskeyChallenger.php     # Deterministic test double
  RebelPasskeysBridgeServiceProvider.php
config/
  rebel-bridge-passkeys.php       # drivers.passkeys toggle
tests/
  Feature/
    PasskeysDriverTest.php        # 17 tests covering all paths

```

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

Part of the [Laravel Rebel](https://github.com/padosoft/laravel-rebel-core) enterprise-auth suite by [Padosoft](https://www.padosoft.com).

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance91

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

51d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/10467699?v=4)[Lorenzo](/maintainers/lopadova)[@lopadova](https://github.com/lopadova)

---

Top Contributors

[![lopadova](https://avatars.githubusercontent.com/u/10467699?v=4)](https://github.com/lopadova "lopadova (4 commits)")

---

Tags

laravelAuthenticationFIDO2webauthnpasskeypadosoftRebelstep-up

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/padosoft-laravel-rebel-bridge-passkeys/health.svg)

```
[![Health](https://phpackages.com/badges/padosoft-laravel-rebel-bridge-passkeys/health.svg)](https://phpackages.com/packages/padosoft-laravel-rebel-bridge-passkeys)
```

###  Alternatives

[defstudio/telegraph

A laravel facade to interact with Telegram Bots

813336.8k3](/packages/defstudio-telegraph)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3914.8k](/packages/rawilk-profile-filament-plugin)[harris21/laravel-fuse

Circuit breaker for Laravel queue jobs. Protect your workers from cascading failures.

45955.7k](/packages/harris21-laravel-fuse)[masterix21/laravel-licensing

Laravel licensing package with polymorphic assignment to any model, activation keys, expirations/renewals, and seat control via LicenseUsage. Supports offline verification with public-key–signed tokens, a CLI to generate/rotate/revoke keys, and an extensible architecture via config and contracts.

1613.3k4](/packages/masterix21-laravel-licensing)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5022.6k](/packages/simplestats-io-laravel-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
