padosoft/laravel-rebel-core

Core primitives, value objects and contracts for Laravel Rebel: the enterprise authentication control plane (AAL/AMR assurance, security context, audit, Sanctum tokens, rate-limiting). The entry point of the padosoft/laravel-rebel-* ecosystem.

Maintainers

Package info

github.com/padosoft/laravel-rebel-core

pkg:composer/padosoft/laravel-rebel-core

Statistics

Installs: 58

Dependents: 11

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-06-02 23:27 UTC

This package is auto-updated.

Last update: 2026-06-03 08:59:03 UTC


README

The heart of the padosoft/laravel-rebel-* ecosystem: an enterprise authentication control plane for Laravel (passwordless OTP, passkey-first, risk-based step-up, SCA, multi-tenant, admin, AI). This -core package contains the shared "building blocks" (value objects, contracts, assurance, audit, hashing) that all the others rest on.

Laravel Rebel

Laravel 12|13 PHP 8.3+ PHPStan max Pest 4 MIT

If this is the first time you see this project, start here: this README explains the WHOLE ecosystem to you.

Table of contents

What Laravel Rebel is (in 1 minute)

Laravel already has Fortify (login, registration, password reset, TOTP 2FA, passkey). Rebel does not replace it: it sits on top and adds what an enterprise/ecommerce product needs:

  • passwordless login (Shopify-style email-OTP) and passkey-first (the most secure);
  • step-up: re-prompting for a strong confirmation only for sensitive actions (change email, credit order, download invoice…), with a security level appropriate to the risk;
  • SCA / PSD2 dynamic linking for payments / credit orders in the EU;
  • SMS/WhatsApp/voice channels with anti-fraud defenses;
  • multi-tenant, audit, web admin panel, AI guard.

Everything is split into small, composable packages: you use only what you need.

In one line: Rebel turns Laravel Fortify into an enterprise authentication control plane.

Glossary (for those who are not auth experts)

Term Plain meaning
OTP "One-Time Password": a single-use code (e.g. 6 digits) sent via email/SMS.
Passwordless Login without a password: you prove it's you with an OTP or a passkey.
Passkey / FIDO2 / WebAuthn A cryptographic credential bound to your device (fingerprint/Face ID/key). It is phishing-resistant.
Phishing-resistant Not replayable on a scam site. Only passkeys are (OTP/SMS are not).
Step-up Raising the verification level for a single sensitive action, not for the whole login.
AAL (1/2/3) "Authenticator Assurance Level" (NIST): how strong the authentication is. AAL1 = 1 factor, AAL2 = 2 factors, AAL3 = hardware.
AMR "Authentication Methods References": how you authenticated (e.g. ['webauthn'], ['otp','email']).
Dynamic linking (PSD2/SCA) The payment confirmation is bound to amount + payee: if they change, it expires.
Pepper A server-side secret key used for the HMACs (so stored email/IP are not reversible).
Tenant A "tenant" in a multi-customer system (e.g. site/brand/country of an ecommerce).

The suite: all the packages

Package What it's for What it does NOT do
laravel-rebel-core (this one) Value objects, contracts, assurance, audit, shared hashing No routes/UI; no Fortify/Twilio/AI
laravel-rebel-email-otp Passwordless email-OTP login (web + mobile/Sanctum) Does not handle SMS (see channels)
laravel-rebel-bridge-fortify Uses Fortify (password-confirm, passkey, TOTP) as a driver + passkey-first login Does not reimplement Fortify
laravel-rebel-step-up Step-up per action/purpose, risk-based, with SCA dynamic linking It is not the login (it's the confirmation of an action)
laravel-rebel-channels Channel/provider abstraction + anti toll-fraud/IRSF + bot gate Not tied to a specific provider
laravel-rebel-channel-twilio Twilio provider (SMS/WhatsApp/Voice, Verify, webhook)
laravel-rebel-recovery High-assurance account recovery + recovery codes
laravel-rebel-sessions Device/session, "log out everywhere", refresh rotation
laravel-rebel-admin-api JSON API control plane (metrics, audit, anomalies) No UI (API only)
laravel-rebel-admin Web Admin Panel (Blade + AJAX + vanilla JS)
laravel-rebel-ai-guard Anomaly detection + AI copilot (explains, does not decide) Does not make destructive decisions on its own
laravel-rebel-auth Meta-package: installs the recommended bundle No business logic

How they fit together (dependency DAG)

                         laravel-rebel-core
                          (common language)
        ┌───────────┬───────────┬─────────────┬───────────────┐
        ▼           ▼           ▼             ▼               ▼
   email-otp     channels    step-up   sessions/recovery   admin-api
        │           │           ▲                              │
        │           └──► channel-twilio                        │
        │                       │                              ▼
        └───────────────► bridge-fortify                     admin (web panel)
                                                                │
                                          ai-guard ── reads ────┘ (buckets/metrics)

Install order: core → email-otp → bridge-fortify → step-up →
channels (+twilio) → admin-api → admin → sessions/recovery → ai-guard.

Rules: the core does not depend on Fortify/Twilio/AI. The admin works without ai-guard. The fortify_password_confirm is web-only (mobile uses a token-native step-up).

Web Admin Panel

The suite includes a web administration panel (package laravel-rebel-admin) — Blade + AJAX + vanilla JS, with no mandatory JS framework — to monitor login/OTP/step-up, provider health, audit, anomalies and compliance.

Laravel Rebel — Web Admin Panel

What this package does (-core)

The core is small and stable: it defines the shared "language". It contains:

  • IdentifiersEmailIdentifier, PhoneIdentifier, GenericIdentifier: normalize and mask email/phone.
  • Keyed hashingKeyedHasher/HmacKeyedHasher: HMAC with a versioned pepper and rotation (for email/IP/OTP).
  • AssuranceAal, AssuranceLevel: the security model that prevents, for example, an email-OTP (AAL1) from "covering" an action that requires a passkey.
  • ContextSecurityContext, TenantContext, DeviceContext: the context of a request (IP/UA already hashed).
  • RiskRiskAssessment, RiskLevel, RecommendedAction.
  • AuthLoginResult (web|token), TokenPair (Sanctum access+refresh).
  • AuditAuditEvent, DatabaseAuditLogger (+ rebel_auth_events table), Redactor (never OTP/secret in the logs).
  • ContractsTokenIssuer, SubjectResolver, TenantResolver, RiskEvaluator, AuditLogger, SessionRegistry, DeviceTrust, BotProtection, RateLimiter, Clock (PSR-20).
  • TenancyCurrentTenant + BelongsToTenant trait (per-tenant isolation).
  • Configphp artisan rebel:validate-config command (fail-fast in CI).

Why Rebel Core vs. the alternatives

There is no drop-in package that gives you a shared auth "core" / contracts layer with first-class NIST assurance, keyed hashing with rotation and built-in audit redaction. The realistic alternatives are: building these primitives by hand, relying on framework-native auth only, or pulling in a heavier all-in-one bundle. Here is how they compare for a shared core that the rest of an auth suite can build on.

Capability Rebel Core Hand-rolled primitives Fortify (framework-native) Spatie permission/multitenancy
First-class NIST AAL/AMR assurance model
satisfies() guard (blocks email-OTP on phishing-resistant purposes)
Keyed HMAC hashing with versioned pepper + rotation
Audit trail with automatic secret redaction
GDPR-safe IP/UA stored as keyed HMAC (never cleartext)
Web/mobile LoginResult + Sanctum TokenPair contract
Per-tenant isolation trait + safe queue worker reset
PSR-20 testable Clock for OTP/step-up expirations
Stable contracts to swap implementations (channels, risk, sessions)
validate-config fail-fast command for CI
Zero hard dependency on Fortify/Twilio/AI
Login/registration/password-reset screens ❌ (by design)

Honest take: Fortify and the Spatie packages are excellent at what they do — Fortify ships the actual auth screens, Spatie handles permissions/multitenancy. Rebel Core is not competing on those; it provides the assurance/audit/hashing/contracts substrate they don't, and it stays unopinionated so you can layer the rest of the suite (or Fortify itself, via bridge-fortify) on top.

End-to-end flows (narrated examples)

1) Passwordless login (ecommerce customer)

user enters their email
  → Rebel creates an OTP challenge, sends the code (anti-enumeration: always the same response)
  → user enters the code
  → atomic verification (single-use) → login
       web    → session + cookie
       mobile → Sanctum TokenPair (access + refresh)
  → audit: email_otp.verified (aal1, amr ['otp','email'])

2) B2B credit order (step-up + SCA)

user clicks "Confirm credit order €1,250 → ACME Srl"
  → middleware rebel.stepup:checkout-credit-order
  → the request requires AAL2 phishing-resistant → passkey preferred
  → the confirmation is BOUND to amount+payee (dynamic linking):
       if the cart total changes → the confirmation expires → re-authenticate
  → action executed, audit with aal/amr and binding

3) Account recovery (the most delicate point)

user has lost access
  → recovery is NOT "email a code": it is a step-up at HIGHER assurance than login
  → single-use recovery code + optional identity verification

Installation (junior-proof)

You usually don't install -core on its own: it comes as a dependency of the other packages. But you can use it stand-alone for its value objects/contracts.

1. Require the package

composer require padosoft/laravel-rebel-core

2. Publish the config (optional)

php artisan vendor:publish --tag=rebel-core-config

3. Set the pepper in .env (secret key for the HMACs)

# generate a strong value:  php -r "echo bin2hex(random_bytes(32));"
REBEL_PEPPER_V1=paste-here-a-long-random-value
REBEL_PEPPER_CURRENT=1

4. (If you use the audit) run the migrations

php artisan migrate

5. Validate the config

php artisan rebel:validate-config
# -> "Configurazione Rebel valida."  (exit 0)

Done. Now the contracts/value objects are available.

Configuration (every option explained)

File: config/rebel-core.php

Key Default What it does When to change it
peppers [1 => env('REBEL_PEPPER_V1')] Map version => secret for the HMACs Add a version to rotate the pepper
pepper_current 1 Version used for new hashes When you rotate, set the new version
hmac_algo sha256 HMAC algorithm Rarely; it must be supported by PHP
hash_ip true Stores the IP as an HMAC (never in cleartext) Leave it true for GDPR
hash_user_agent true Stores the User-Agent as an HMAC Leave it true for GDPR

Pepper rotation (example):

// config/rebel-core.php
'peppers' => [
    1 => env('REBEL_PEPPER_V1'),
    2 => env('REBEL_PEPPER_V2'), // new secret
],
'pepper_current' => 2, // new hashes use v2; the old ones (v1) remain verifiable

Usage examples

Identifiers (normalization + masking)

use Padosoft\Rebel\Core\Identifiers\EmailIdentifier;
use Padosoft\Rebel\Core\Identifiers\PhoneIdentifier;

$email = EmailIdentifier::from('  Mario.Rossi@Example.IT ');
$email->normalized(); // "mario.rossi@example.it"  (for lookup/HMAC)
$email->masked();     // "m***@example.it"          (for UI/log)

PhoneIdentifier::from('+39 328 000 0000')->normalized(); // "+393280000000"

Keyed hashing (with rotation)

use Padosoft\Rebel\Core\Contracts\KeyedHasher;

$hasher = app(KeyedHasher::class);
$h = $hasher->hash($email->normalized());   // HashedValue(hash, keyVersion)
$hasher->matches($email->normalized(), $h->hash, $h->keyVersion); // true (constant-time)

Assurance (the central security rule)

use Padosoft\Rebel\Core\Assurance\Aal;
use Padosoft\Rebel\Core\Assurance\AssuranceLevel;

$emailOtp = new AssuranceLevel(Aal::Aal1, phishingResistant: false, amr: ['otp', 'email']);
$passkey  = new AssuranceLevel(Aal::Aal2, phishingResistant: true,  amr: ['webauthn']);

// An action that requires AAL2 phishing-resistant:
$emailOtp->satisfies(Aal::Aal2, requirePhishingResistant: true); // false  ← email-OTP is not enough
$passkey->satisfies(Aal::Aal2, requirePhishingResistant: true);  // true

SecurityContext from a request

use Padosoft\Rebel\Core\Context\SecurityContext;
use Padosoft\Rebel\Core\Contracts\KeyedHasher;

$ctx = SecurityContext::fromRequest($request, app(KeyedHasher::class))
    ->withGuard('customers')
    ->withPurpose('customer-login')
    ->withIdentifier($email);
// $ctx->ipHmac / $ctx->userAgentHash are already hashed (never in cleartext)

Audit (with automatic secret redaction)

use Padosoft\Rebel\Core\Audit\AuditEvent;
use Padosoft\Rebel\Core\Audit\AuthEventType;
use Padosoft\Rebel\Core\Contracts\AuditLogger;

app(AuditLogger::class)->record(new AuditEvent(
    type: AuthEventType::EmailOtpVerified->value,
    guard: 'customers',
    identifierHmac: $h->hash, keyVersion: $h->keyVersion,
    purpose: 'customer-login',
    aal: Aal::Aal1, amr: ['otp', 'email'],
    metadata: ['otp' => '123456'], // ← will be stored as "[REDACTED]"
));

Tenancy (per-tenant isolation)

use Padosoft\Rebel\Core\Tenancy\CurrentTenant;

app(CurrentTenant::class)->set('site-it'); // usually done by the TenantResolver/middleware
// All Rebel models with BelongsToTenant now filter and stamp tenant_id = 'site-it'

Testable time (PSR-20 Clock)

use Padosoft\Rebel\Core\Clock\FakeClock;
use Psr\Clock\ClockInterface;

$clock = new FakeClock(new DateTimeImmutable('2026-01-01 10:00:00'));
app()->instance(ClockInterface::class, $clock);
$clock->advance(600); // simulates +10 minutes (useful to test OTP/step-up expirations)

Compliance

Rebel is designed by-design on recognized standards (details in the ADRs/docs/):

  • NIST 800-63B-4 — AAL/AMR model; email-OTP = AAL1; SMS = "restricted"; only passkeys are phishing-resistant.
  • PSD2 / SCA — dynamic linking for B2B credit orders (does not replace the PSP's 3DS2 for cards).
  • GDPR — IP/identifiers as keyed HMAC + key_version (rotation), log redaction, no PII in cleartext.

See docs/adr/ADR-0005-design-lock.md.

Testing

composer test       # Pest
composer phpstan    # static analysis (max level)
composer pint       # code style

License

MIT — see LICENSE. © Padosoft.