padosoft / laravel-rebel-recovery
High-assurance account recovery for Laravel Rebel: single-use HMAC-hashed recovery (backup) codes, generated once at enrolment, with anti-ATO checks. Part of padosoft/laravel-rebel-*.
Package info
github.com/padosoft/laravel-rebel-recovery
pkg:composer/padosoft/laravel-rebel-recovery
Requires
- php: ^8.3
- illuminate/contracts: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- padosoft/laravel-rebel-core: ^0.1
- spatie/laravel-package-tools: ^1.92
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
This package is auto-updated.
Last update: 2026-06-03 12:24:58 UTC
README
Single-use recovery codes done right. Backup codes the user can fall back to when they lose their device — generated once, stored only as keyed HMACs, verified in constant time, single-use, and easy to type. Part of the
padosoft/laravel-rebel-*suite.
Table of contents
- What it is
- Why this package
- Rebel Recovery vs the alternatives
- Installation
- Usage
- Security notes
.env.example- Testing & License
What it is
High-assurance account-recovery building blocks for Rebel. v0.1.0 ships recovery
(backup) codes: a set of one-time codes a user can use to recover access. Pair them with a
high-assurance step-up purpose (from laravel-rebel-step-up) to gate sensitive recovery flows.
Depends on padosoft/laravel-rebel-core.
Why this package
| ★ | What | In short |
|---|---|---|
| ★★★ | Hashed at rest | Codes are stored as a keyed HMAC of (salt|code) with a key_version — never plaintext, pepper-rotation friendly. |
| ★★★ | Single-use & atomic | Verification locks the row and consumes the code; a code works exactly once. |
| ★★★ | Constant-time verify | All candidates are checked without early-exit, so timing doesn't leak which code matched. |
| ★★ | Typo-tolerant | Input is normalized (case, separators, Crockford O→0 / I·L→1), so a correct code never fails on format. |
| ★★ | Regenerate safely | Generating a new set invalidates all previous unconsumed codes. |
| ★★ | Audited & multi-tenant | Generate/complete/fail are recorded; rows are tenant-scoped. |
Rebel Recovery vs the alternatives
| Capability | Rebel Recovery | Shopify | Fortify recovery codes | Hand-rolled |
|---|---|---|---|---|
| Self-issued recovery codes for your users | ✅ | ❌ | ✅ | ➖ |
| Codes hashed at rest (you control storage) | ✅ | ❌ | ➖ (encrypted blob) | ❌ |
| Keyed HMAC + key_version (pepper rotation) | ✅ | ❌ | ❌ | ❌ |
| Constant-time verification | ✅ | ➖ | ➖ | ❌ |
| Atomic single-use (row lock) | ✅ | ➖ | ➖ | ❌ |
| Input normalization (typo-tolerant) | ✅ | ➖ | ❌ | ❌ |
| ~100-bit entropy codes | ✅ | ➖ | ➖ (~50 bits) | ➖ |
| Audited + multi-tenant (your app) | ✅ | ❌ | ❌ | ❌ |
Legend: ✅ built-in · ➖ partial / hosted-only / not exposed to you · ❌ not available.
Note: Shopify is a hosted, closed commerce platform — its own staff/customer logins may have backup codes, but it never exposes a self-hostable, HMAC-hashed, single-use recovery-code API you can issue to your application's users.
Installation
composer require padosoft/laravel-rebel-recovery
php artisan vendor:publish --tag="rebel-recovery-migrations"
php artisan migrate
Usage
use Padosoft\Rebel\Recovery\RecoveryCodeManager; $recovery = app(RecoveryCodeManager::class); // At enrolment: generate and SHOW ONCE (cannot be retrieved again) $codes = $recovery->generate($user, count: 10); // → ["A1B2-C3D4-E5F6-G7H8-J9K0", ...] display/download these now // Later: the user submits one code to recover access if ($recovery->verify($user, $request->string('code'))) { // consumed — let them through (ideally behind a high-assurance step-up) } $recovery->remaining($user); // how many codes are left
Security notes
- Never stored in plaintext: only a keyed HMAC of
(salt|code)with akey_version. - Single-use & atomic: a row lock guarantees a code can be redeemed once.
- Constant-time: the whole candidate set is checked, no early-exit timing leak.
- No built-in lockout: recovery codes are a lookup secret — put your app's brute-force
throttle in front of
verify()(and treat repeated failures as an anti-ATO signal). - Multi-tenant: rows are scoped via the core tenant scope — resolve the current tenant per request in multi-tenant deployments so codes never cross tenants.
.env.example
REBEL_RECOVERY_CODE_COUNT=10
Testing & License
composer test # Pest (generate, single-use verify, reuse, regenerate, per-subject, normalization) composer phpstan # static analysis, level max composer pint # code style
License: MIT — see LICENSE. Part of the padosoft/laravel-rebel suite.
