padosoft / laravel-rebel-bridge-laragear-2fa
Bridge between laragear/two-factor and Laravel Rebel: exposes TOTP as an AAL2 step-up driver, integrates recovery codes, and emits full audit telemetry into the Rebel audit trail. Part of padosoft/laravel-rebel-*.
Package info
github.com/padosoft/laravel-rebel-bridge-laragear-2fa
pkg:composer/padosoft/laravel-rebel-bridge-laragear-2fa
Requires
- php: ^8.3
- illuminate/contracts: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- padosoft/laravel-rebel-core: ^0.1
- padosoft/laravel-rebel-step-up: ^0.1
- spatie/laravel-package-tools: ^1.92
Requires (Dev)
- laragear/two-factor: ^4.0
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- padosoft/laravel-rebel-email-otp: ^0.1
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
Suggests
- laragear/two-factor: Enables the laragear_totp step-up driver and TOTP/recovery-code verification. Install it and add the TwoFactorAuthentication trait to your User model.
This package is auto-updated.
Last update: 2026-06-04 04:35:25 UTC
README
laravel-rebel-bridge-laragear-2fa
TOTP step-up authentication for Laravel Rebel, powered by laragear/two-factor.
What this package does
This package bridges laragear/two-factor — the popular on-premises TOTP library for Laravel — into the Laravel Rebel enterprise authentication control plane.
It exposes laragear's Time-based One-Time Password (TOTP) two-factor authentication as a Rebel step-up driver. Once installed, a Rebel step-up policy can require a user to confirm a TOTP code (or a single-use recovery code) before accessing a sensitive resource. The result is stored in the Rebel audit trail with full compliance metadata (AAL2, AMR, channel, purpose) so your dashboards, funnel analytics, and GDPR audit explorer all populate correctly.
Where it fits in the Laravel Rebel suite
+------------------------------------------+
| Your Application |
| Route::middleware('rebel.stepup:pay') |
+--------------------+---------------------+
| requires step-up
+--------------------v---------------------+
| laravel-rebel-step-up |
| DriverRegistry <-- this bridge |
| v laragear_totp driver |
+--------------------+---------------------+
| validates via
+--------------------v---------------------+
| laragear/two-factor |
| User::validateTwoFactorCode($code) |
+------------------------------------------+
Glossary
| Term | Meaning |
|---|---|
| TOTP | Time-based One-Time Password (RFC 6238). A 6-digit code that changes every 30 seconds, generated by an authenticator app (Google Authenticator, Authy, 1Password). |
| 2FA | Two-Factor Authentication. The user proves identity with two factors: something they know (password) + something they have (authenticator app). |
| Step-up | A re-confirmation challenge triggered before a specific sensitive action (e.g. changing a bank account, approving a payment), even when the user is already logged in. Different from login 2FA. |
| AAL2 | Authentication Assurance Level 2 (NIST SP 800-63). The user has proved possession of a second factor. AAL2 is required for high-value transactions. |
| AMR | Authentication Methods Reference (RFC 8176). A list of strings describing HOW the user authenticated, e.g. ['otp', 'totp']. |
| Recovery code | A one-time backup code generated when 2FA is first set up. Used when the user loses their authenticator app. Single-use: once redeemed, it is consumed and cannot be used again. |
| Phishing resistance | A property of some authentication methods (e.g. passkeys/FIDO2) that prevents even a man-in-the-middle from relaying a captured response. TOTP is NOT phishing-resistant because a live phishing attack can capture and relay a valid 30-second code window. |
| AuditLogger | The Rebel core contract that persists authentication events to rebel_auth_events. Never uses session, supports queue dispatch (Horizon-ready). |
| Seam | A design pattern that inserts a thin interface between two components so they can be tested independently. This package introduces TwoFactorValidator as a seam over laragear. |
Installation
1. Install the bridge
composer require padosoft/laravel-rebel-bridge-laragear-2fa
The package auto-discovers its service provider. If you disabled auto-discovery, add it manually:
// config/app.php 'providers' => [ Padosoft\Rebel\Bridge\Laragear2fa\RebelLaragear2faBridgeServiceProvider::class, ],
2. Install laragear/two-factor
composer require laragear/two-factor
Publish and run laragear's migration:
php artisan vendor:publish --provider="Laragear\TwoFactor\TwoFactorServiceProvider" --tag="migrations" php artisan migrate
This creates the two_factor_authentications table (laragear stores 2FA data in a related table,
not on the users table itself).
3. Add the trait to your User model
use Illuminate\Foundation\Auth\User as Authenticatable; use Laragear\TwoFactor\TwoFactorAuthentication; use Laragear\TwoFactor\Contracts\TwoFactorAuthenticatable; class User extends Authenticatable implements TwoFactorAuthenticatable { use TwoFactorAuthentication; // ... }
4. Publish the bridge config (optional)
php artisan vendor:publish --tag="rebel-bridge-laragear-2fa-config"
Configuration
After publishing, edit config/rebel-bridge-laragear-2fa.php:
| Key | Default | Env var | Description |
|---|---|---|---|
drivers.laragear_totp |
true |
REBEL_LARAGEAR_DRIVER_TOTP |
Register the TOTP step-up driver into the Rebel DriverRegistry. Set to false to completely disable TOTP step-up without uninstalling the package. The driver is only registered when this is true AND laragear is installed. |
use_recovery_codes |
true |
REBEL_LARAGEAR_USE_RECOVERY_CODES |
Accept a single-use recovery code as an alternative to a TOTP code. Set to false to restrict step-up strictly to live TOTP codes (e.g. for high-security contexts where recovery codes are managed out-of-band). |
How it works — step by step
1. User is authenticated and tries to access a sensitive route.
2. The rebel.stepup:pay middleware checks if the purpose 'pay' is confirmed.
3. It is not — the step-up manager picks the laragear_totp driver.
4. start() returns null — no server-side challenge. The user opens their
authenticator app and reads the current 6-digit code.
5. The user submits the code.
6. verify() calls LaragearTwoFactorValidator which calls:
$user->validateTwoFactorCode($code, useRecoveryCodes: false)
- TOTP match: returns true. Laragear checks the code against the user's TOTP
secret, using a cache to prevent replay within the same 30-second window.
- TOTP miss: if use_recovery_codes is true, calls validateTwoFactorCode again
with useRecoveryCodes: true, which checks the single-use recovery code list
and marks the matching code consumed on match.
7. On success: AuditLogger records 'stepup.laragear_totp.verified' (and optionally
'stepup.laragear_totp.recovery_code.used') to rebel_auth_events.
8. On failure: AuditLogger records 'stepup.laragear_totp.failed'.
The step-up manager rejects the request. Any Throwable is caught and treated
as failure (fail-closed).
Usage examples
Example 1 — Protecting a route with step-up middleware
// routes/web.php Route::post('/account/payment-method', [PaymentController::class, 'store']) ->middleware('auth', 'rebel.stepup:change-payment');
Define the purpose in your step-up config:
// config/rebel-step-up.php 'purposes' => [ 'change-payment' => [ 'required_assurance' => 'aal2', 'drivers' => ['laragear_totp'], 'always_require' => true, ], ],
Example 2 — Manual step-up in a controller
use Padosoft\Rebel\StepUp\RebelStepUp; use Padosoft\Rebel\StepUp\StepUpContext; use Padosoft\Rebel\Core\Context\SecurityContext; use Padosoft\Rebel\Core\Contracts\KeyedHasher; class TransferController extends Controller { public function confirm(Request $request, RebelStepUp $stepUp, KeyedHasher $hasher): JsonResponse { $ctx = new StepUpContext( subject: $request->user(), purpose: 'bank-transfer', security: SecurityContext::fromRequest($request, $hasher), ); // Start the step-up challenge (returns null for TOTP — no server-side challenge). $start = $stepUp->start($ctx); // ... send the challengeId back to the browser, user enters their TOTP code ... // When the user submits the code: $result = $stepUp->confirm($start->challengeId, $request->input('code'), $ctx); if (! $result->success) { return response()->json(['error' => 'Step-up failed. Please check your authenticator app.'], 403); } // Proceed with the sensitive operation. return response()->json(['status' => 'transferred']); } }
Example 3 — Mobile / Sanctum API with step-up check
use Padosoft\Rebel\StepUp\RebelStepUp; use Padosoft\Rebel\StepUp\StepUpContext; class AccountController extends Controller { public function deleteAccount(Request $request, RebelStepUp $stepUp, KeyedHasher $hasher): JsonResponse { $ctx = new StepUpContext( subject: $request->user(), purpose: 'delete-account', security: SecurityContext::fromRequest($request, $hasher), ); if (! $stepUp->isConfirmed($ctx)) { return response()->json([ 'step_up_required' => true, 'driver' => 'laragear_totp', 'message' => 'Please confirm with your authenticator app to continue.', ], 403); } // Safe to proceed. $request->user()->delete(); return response()->json(['status' => 'account deleted']); } }
Example 4 — Using the FakeTwoFactorValidator in feature tests (offline, no DB)
use Padosoft\Rebel\Bridge\Laragear2fa\Contracts\TwoFactorValidator; use Padosoft\Rebel\Bridge\Laragear2fa\Testing\FakeTwoFactorValidator; beforeEach(function (): void { $this->user = User::factory()->create(); // Bind the fake: this user has 2FA enabled, '123456' is the valid TOTP code. app()->instance(TwoFactorValidator::class, new FakeTwoFactorValidator( enabled: [(string) $this->user->getAuthIdentifier()], validCodes: ['123456'], validRecoveryCodes: ['AAAA-BBBB'], )); }); it('allows access after a valid TOTP code', function (): void { $this->actingAs($this->user) ->postJson('/account/confirm-step-up', ['code' => '123456']) ->assertOk(); }); it('rejects access with an invalid code', function (): void { $this->actingAs($this->user) ->postJson('/account/confirm-step-up', ['code' => '000000']) ->assertForbidden(); }); it('accepts a recovery code', function (): void { $this->actingAs($this->user) ->postJson('/account/confirm-step-up', ['code' => 'AAAA-BBBB']) ->assertOk(); // Second use of the same recovery code must fail (single-use). $this->actingAs($this->user) ->postJson('/account/confirm-step-up', ['code' => 'AAAA-BBBB']) ->assertForbidden(); });
Audit telemetry
Every step-up attempt writes one or more rows to rebel_auth_events. The bridge produces three
event types:
| Event type | When emitted | Channel | AMR |
|---|---|---|---|
stepup.laragear_totp.verified |
TOTP code OR recovery code accepted | totp |
['otp','totp'] |
stepup.laragear_totp.failed |
Code wrong, 2FA not enabled, or any error | totp |
['otp','totp'] |
stepup.laragear_totp.recovery_code.used |
Recovery code redeemed (emitted before .verified) |
totp |
['recovery_code'] |
All events include subject_type, subject_id, aal (aal2 on verified), channel, and amr.
The TOTP code, any TOTP secret, and recovery codes are never written to the audit trail or any log.
Card-battle: TOTP step-up comparison
| Feature | laravel-rebel-bridge-laragear-2fa | Laravel Fortify (built-in 2FA) | Authy SDK | Shopify (platform MFA) |
|---|---|---|---|---|
| TOTP step-up (not just login 2FA) | Yes | No — only at login | No step-up API | No developer API |
| Single-use recovery codes | Yes | Yes | No | No |
| AAL2 assurance declaration | Yes | No | No | No |
AMR ['otp','totp'] telemetry |
Yes | No | No | No |
Audit trail (rebel_auth_events) |
Yes | No | No | No |
| Compliance-ready (PSD2 / NIST 800-63) | Yes | No | No | No |
| Offline tests (no DB/TOTP engine needed) | Yes — FakeTwoFactorValidator |
No — needs Google2FA + Crypt | No | No |
| PHPStan level max | Yes | No | No | No |
| Config-gated driver registration | Yes | No | No | No |
| Fail-closed on any Throwable | Yes | No | No | No |
| Recovery-code single-use enforcement | Yes | Yes | No | No |
| Phishing-resistant | No (by design — TOTP is not) | No | No | No |
| Laravel 12 / 13 support | Yes | Yes | No | No |
| Open source / self-hosted | Yes | Yes | No — SaaS | No — closed |
🔋 Vibe coding with batteries included
This package ships everything you need to develop, extend, and test it with an AI coding assistant:
CLAUDE.md— working guide for Claude Code and other AI agents: conventions, architecture, laragear API methods, config keys, Definition of Done.AGENTS.md— operational rules: branching strategy, local loop, GitHub gate, security guardrails..claude/skills/rebel-package-dev/— an invocable skill that encodes the TDD loop, PHPStan-max recipes, security rules, and the release procedure. Invoke it with/rebel-package-devbefore non-trivial work.Testing\FakeTwoFactorValidator— deterministic offline fake: configure which users have 2FA enabled, which TOTP codes pass (not consumed), and which recovery codes are available (single-use). Tests run in under 2 seconds with no external dependencies.
Drop into the project directory and start with /rebel-package-dev for a guided dev session.
Contributing
See AGENTS.md for the full contributing workflow (branch naming, Definition of Done, CI gates). PRs are welcome; please keep the PHPStan level max green and include Pest tests.
License
MIT — see LICENSE.
Credits
Built by Padosoft. Part of the Laravel Rebel enterprise authentication suite.
Uses laragear/two-factor by Italo Israel Baeza Cabrera.
