PHPackages                             padosoft/laravel-rebel-bridge-spatie-otp - 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-spatie-otp

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

padosoft/laravel-rebel-bridge-spatie-otp
========================================

Bridge between spatie/laravel-one-time-passwords and Laravel Rebel: exposes email/SMS OTP as an AAL2 step-up driver with full audit telemetry. Part of padosoft/laravel-rebel-\*.

v0.1.0(1mo ago)02[1 PRs](https://github.com/padosoft/laravel-rebel-bridge-spatie-otp/pulls)MITPHPPHP ^8.3CI failing

Since Jun 4Pushed 1mo agoCompare

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

READMEChangelog (1)Dependencies (11)Versions (4)Used By (0)

> Official documentation:

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

laravel-rebel-bridge-spatie-otp
===============================

[](#laravel-rebel-bridge-spatie-otp)

**AAL2 step-up authentication for Laravel apps already using `spatie/laravel-one-time-passwords`.**

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

[![CI](https://github.com/padosoft/laravel-rebel-bridge-spatie-otp/actions/workflows/ci.yml/badge.svg)](https://github.com/padosoft/laravel-rebel-bridge-spatie-otp/actions)[![Packagist](https://camo.githubusercontent.com/10aff7479f89acb65b280b171c1a6022040cab527954057fa99f00939e37fc70/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7061646f736f66742f6c61726176656c2d726562656c2d6272696467652d7370617469652d6f74702e737667)](https://packagist.org/packages/padosoft/laravel-rebel-bridge-spatie-otp)[![License: MIT](https://camo.githubusercontent.com/784362b26e4b3546254f1893e778ba64616e362bd6ac791991d2c9e880a3a64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e737667)](LICENSE)

---

What this package does
----------------------

[](#what-this-package-does)

This bridge exposes `spatie/laravel-one-time-passwords` as a **step-up driver** in the Laravel Rebel `DriverRegistry`. Once registered, the Rebel step-up manager can challenge users with a one-time password (delivered by Spatie's notification system — email, SMS, or any custom channel) as a second factor to confirm a sensitive action ("step-up authentication").

**In plain English:** when a user wants to do something risky (delete their account, make a large transfer, change their email), your app asks them to prove they're still present by typing a fresh 6-digit code that was just sent to their inbox or phone. This package wires up the existing Spatie OTP you've already installed to do exactly that.

---

Glossary
--------

[](#glossary)

TermMeaning**OTP**One-Time Password — a short numeric code valid for a single use and a short time window.**Step-up**Asking an already-authenticated user to prove presence again before a sensitive action.**AAL**Authenticator Assurance Level (NIST 800-63). AAL1 = password only; AAL2 = password + second factor.**AMR**Authentication Methods References — a list of methods used, e.g. `['otp']`.**Phishing-resistant**A factor that cannot be captured and replayed by a phishing site (e.g. passkey/WebAuthn). OTP is NOT phishing-resistant.**DriverRegistry**Rebel's runtime registry of available step-up drivers; each driver declares its key and assurance level.**AuditLogger**Core Rebel contract that records auth events to `rebel_auth_events` for the compliance panel.**HasOneTimePasswords**Spatie's PHP trait — add this to your User model to enable OTP delivery.---

How it works (step by step)
---------------------------

[](#how-it-works-step-by-step)

```
User triggers a protected action
        │
        ▼
Rebel StepUp Manager
  → looks up driver 'spatie_otp' in DriverRegistry
  → calls driver.isAvailableFor($user)       ← checks user has HasOneTimePasswords trait
        │
        ▼
driver.start($context)
  → calls $user->sendOneTimePassword()       ← Spatie creates DB record + dispatches notification
  → emits AuditEvent: stepup.spatie_otp.started
  → returns opaque reference (null for Spatie's stateful flow)
        │
        ▼
User receives OTP (email / SMS / custom notification)
        │
        ▼
driver.verify($context, $input, $reference)
  → calls $user->consumeOneTimePassword($input)  ← Spatie checks DB record
  → ConsumeOneTimePasswordResult::Ok  →  true   + AuditEvent: stepup.spatie_otp.verified
  → any other result                 →  false  + AuditEvent: stepup.spatie_otp.failed

```

The OTP code **never appears in the audit log** (only the event type, channel, and subject identity).

---

Installation
------------

[](#installation)

### 1. Require the packages

[](#1-require-the-packages)

```
composer require padosoft/laravel-rebel-bridge-spatie-otp
composer require spatie/laravel-one-time-passwords
```

> `spatie/laravel-one-time-passwords` is a **suggested** dependency. This bridge installs cleanly without it — the driver simply stays unregistered until Spatie is present.

### 2. Add the Spatie migration and config

[](#2-add-the-spatie-migration-and-config)

```
php artisan vendor:publish --provider="Spatie\OneTimePasswords\OneTimePasswordsServiceProvider"
php artisan migrate
```

### 3. Add the trait to your User model

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

```
use Spatie\OneTimePasswords\Models\Concerns\HasOneTimePasswords;

class User extends Authenticatable
{
    use HasOneTimePasswords;
    // ...
}
```

### 4. (Optional) Publish the Rebel bridge config

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

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

---

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

[](#configuration)

File: `config/rebel-bridge-spatie-otp.php`

KeyTypeDefaultDescription`drivers.spatie_otp`bool`true`Enable or disable the `spatie_otp` step-up driver.`audit_channel`string`'otp'`Channel label in audit events (`rebel_auth_events.channel`). Change to `'email'` or `'sms'` to match your Spatie delivery method.You can also control the driver via the `REBEL_SPATIE_OTP_DRIVER_ENABLED` and `REBEL_SPATIE_OTP_AUDIT_CHANNEL` environment variables.

---

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

[](#usage-examples)

### Example 1 — Protect a sensitive route with step-up middleware

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

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

Configure the purpose policy (e.g. in `config/rebel-step-up.php`):

```
'policies' => [
    'delete-account' => [
        'required_aal'            => 'aal2',
        'phishing_resistant'      => false,
        'preferred_drivers'       => ['spatie_otp'],
    ],
],
```

### Example 2 — Start a step-up challenge manually

[](#example-2--start-a-step-up-challenge-manually)

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

$stepUp = app(RebelStepUp::class);

$context = new StepUpContext(
    subject: $request->user(),
    purpose: 'change-email',
    security: SecurityContext::fromRequest($request, app(KeyedHasher::class)),
);

// Starts the challenge: Rebel picks the best available driver (spatie_otp if enabled)
// and calls driver->start(), which sends the OTP via Spatie.
$challenge = $stepUp->start($context, driverKey: 'spatie_otp');
```

### Example 3 — Verify the user's input

[](#example-3--verify-the-users-input)

```
// The user submits the 6-digit code from their email.
$result = $stepUp->verify($challenge->id, $request->input('otp'), $context);

if ($result->success) {
    // Proceed with the sensitive action.
} else {
    return back()->withErrors(['otp' => 'Invalid or expired code. Please try again.']);
}
```

### Example 4 — Test your controller with a fake broker

[](#example-4--test-your-controller-with-a-fake-broker)

In your feature tests, swap the broker for a fully in-memory fake — no mail, no DB:

```
use Padosoft\Rebel\Bridge\SpatieOtp\Contracts\OneTimePasswordBroker;
use Padosoft\Rebel\Bridge\SpatieOtp\Testing\FakeOneTimePasswordBroker;

// In setUp() or a service provider override:
$fake = new FakeOneTimePasswordBroker(validCode: '123456');
app()->instance(OneTimePasswordBroker::class, $fake);

// Trigger the step-up (sends OTP via fake, no mail dispatched).
$this->post('/account/delete')->assertRedirect(); // redirected to step-up challenge

// Provide the correct code.
$this->post('/step-up/verify', ['otp' => '123456'])->assertOk();
expect($fake->sendCount)->toBe(1);
```

### Example 5 — Disable the driver temporarily

[](#example-5--disable-the-driver-temporarily)

In `.env`:

```
REBEL_SPATIE_OTP_DRIVER_ENABLED=false

```

Or in code (e.g. in a feature flag service provider):

```
config(['rebel-bridge-spatie-otp.drivers.spatie_otp' => false]);
```

---

Assurance declaration
---------------------

[](#assurance-declaration)

PropertyValueDriver key`spatie_otp`AAL`Aal::Aal2`Phishing-resistantNoAMR`['otp']`---

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

[](#audit-events)

Every step-up operation emits an event to `rebel_auth_events` via `AuditLogger`. The OTP code is **never** logged.

Event typeWhen`stepup.spatie_otp.started``start()` called, OTP sent successfully`stepup.spatie_otp.verified``verify()` returned true (correct code)`stepup.spatie_otp.failed``verify()` returned false (wrong / expired / rate-limited)Each event carries: `subjectType`, `subjectId`, `channel` (configurable), `provider: 'spatie_otp'`, `purpose`, `aal: Aal2`, `amr: ['otp']`.

---

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

[](#competitor-card-battle)

How does Rebel's spatie OTP bridge compare to rolling your own, or using alternative SaaS providers?

Feature**Laravel Rebel + Spatie OTP**Roll-your-own OTPTwilio VerifyShopify MFAOffline testable (FakeOtpBroker)✅❌ Requires mail/DB❌ Requires API❌ SaaS onlyAAL/AMR compliance metadata✅❌ DIY❌ Not exposed❌ Not exposedAudit trail to DB (rebel\_auth\_events)✅❌ DIY❌❌Purpose-scoped step-up policies✅❌❌❌Config-gated driver toggle✅❌N/AN/AZero hard dep (installs without Spatie)✅N/AN/AN/AFail-closed on broker Throwable✅❌ Depends on impl❌❌Custom delivery channel (email/SMS)✅ via Spatie notification❌ DIY✅❌Open-source &amp; self-hosted✅✅❌❌Seam pattern (swappable broker)✅❌❌❌---

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

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

This package ships everything you need to start building immediately:

- **`CLAUDE.md`** — AI session guide (design notes, key decisions, session startup checklist).
- **`AGENTS.md`** — operative rules for every contributor (human or AI): branching, CI gates, DoD.
- **`.claude/skills/rebel-package-dev/SKILL.md`** — the dev loop (TDD, PHPStan-max recipes, security rules, Spatie-specific gotchas).
- **`Testing\FakeOneTimePasswordBroker`** — fully in-memory offline test double.
- **`config/rebel-bridge-spatie-otp.php`** — documented config file.
- **CI matrix** — PHP 8.3/8.4/8.5 × Laravel 12/13, quality job (Pint + PHPStan level max).

---

Requirements
------------

[](#requirements)

DependencyVersionPHP`^8.3`Laravel`^12` or `^13`padosoft/laravel-rebel-core`^0.1`padosoft/laravel-rebel-step-up`^0.1`spatie/laravel-one-time-passwords`^1.0` *(suggested)*---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance91

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity41

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

spatielaravelotpAuthenticationone-time-passwordpadosoftRebelstep-up

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

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

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

###  Alternatives

[spatie/laravel-permission

Permission handling for Laravel 12 and up

12.9k102.4M1.5k](/packages/spatie-laravel-permission)[defstudio/telegraph

A laravel facade to interact with Telegram Bots

813336.8k3](/packages/defstudio-telegraph)[spatie/laravel-passkeys

Use passkeys in your Laravel app

472890.7k40](/packages/spatie-laravel-passkeys)[harris21/laravel-fuse

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

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

Profile &amp; MFA starter kit for filament.

3914.8k](/packages/rawilk-profile-filament-plugin)[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)

PHPackages © 2026

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