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-*.

Maintainers

Package info

github.com/padosoft/laravel-rebel-bridge-laragear-2fa

pkg:composer/padosoft/laravel-rebel-bridge-laragear-2fa

Statistics

Installs: 2

Dependents: 1

Suggesters: 1

Stars: 0

Open Issues: 0

v0.1.1 2026-06-04 04:35 UTC

This package is auto-updated.

Last update: 2026-06-04 04:35:25 UTC


README

Laravel Rebel

laravel-rebel-bridge-laragear-2fa

TOTP step-up authentication for Laravel Rebel, powered by laragear/two-factor.

Latest Version on Packagist CI PHPStan Level Max License: MIT

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-dev before 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.