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

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

padosoft/laravel-rebel-email-otp
================================

Enterprise passwordless email-OTP login for Laravel Rebel: anti-enumeration, multi-dimensional rate-limiting, multi-tenant/purpose/risk, Sanctum token issuance. Part of padosoft/laravel-rebel-\*.

v0.1.0(1mo ago)0344↓90%5MITPHPPHP ^8.3CI passing

Since Jun 3Pushed 1mo agoCompare

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

READMEChangelog (1)Dependencies (9)Versions (3)Used By (5)

Laravel Rebel — Email OTP
=========================

[](#laravel-rebel--email-otp)

> Official documentation:

> **Passwordless login via email-OTP, enterprise-grade.** Email → code → access, Shopify-style, but with real anti-enumeration, rate-limiting/abuse protection, multi-tenant support, atomic single-use verification and **Sanctum token** issuance for mobile. Part of the `padosoft/laravel-rebel-*` suite.

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

 [![Laravel 12|13](https://camo.githubusercontent.com/9e9b743bcbf97a29fe735334a4a8e906d05d60310969905af6607cef8da30138/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d313225323025374325323031332d4646324432303f7374796c653d666c61742d737175617265266c6f676f3d6c61726176656c266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/9e9b743bcbf97a29fe735334a4a8e906d05d60310969905af6607cef8da30138/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d313225323025374325323031332d4646324432303f7374796c653d666c61742d737175617265266c6f676f3d6c61726176656c266c6f676f436f6c6f723d7768697465) [![PHP 8.3+](https://camo.githubusercontent.com/6aa777dd33ef43fbef727d8187b578003a61e5dc41bbc958b0938c996cdc92f2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e33253230253743253230382e34253230253743253230382e352d3737374242343f7374796c653d666c61742d737175617265266c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/6aa777dd33ef43fbef727d8187b578003a61e5dc41bbc958b0938c996cdc92f2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e33253230253743253230382e34253230253743253230382e352d3737374242343f7374796c653d666c61742d737175617265266c6f676f3d706870266c6f676f436f6c6f723d7768697465) [![PHPStan max](https://camo.githubusercontent.com/4b9a3c97d76534abb905e64bd9e5bb9f13fe68e962071e0ccbbe7b629112f11c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6d61782d3241364644423f7374796c653d666c61742d737175617265)](https://camo.githubusercontent.com/4b9a3c97d76534abb905e64bd9e5bb9f13fe68e962071e0ccbbe7b629112f11c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6d61782d3241364644423f7374796c653d666c61742d737175617265) [![Pest 4](https://camo.githubusercontent.com/9b9da1d7d243a7465ab338e9374e47300a7fe2e26b5c291e7e3c95b53153789a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d50657374253230342d3232433535453f7374796c653d666c61742d737175617265)](https://camo.githubusercontent.com/9b9da1d7d243a7465ab338e9374e47300a7fe2e26b5c291e7e3c95b53153789a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d50657374253230342d3232433535453f7374796c653d666c61742d737175617265) [![MIT](https://camo.githubusercontent.com/ac049ef4e7a0b7196b09add6ac2d4f180e544c0ac779c2b2ac2fd2723a209579/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75653f7374796c653d666c61742d737175617265)](https://camo.githubusercontent.com/ac049ef4e7a0b7196b09add6ac2d4f180e544c0ac779c2b2ac2fd2723a209579/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75653f7374796c653d666c61742d737175617265)

---

Table of contents
-----------------

[](#table-of-contents)

- [What it is (and what it is NOT)](#what-it-is-and-what-it-is-not)
- [Quick glossary](#quick-glossary)
- [Why Rebel Email-OTP — the moats](#why-rebel-email-otp--the-moats)
- [Rebel vs the others (card-battle)](#rebel-vs-the-others-card-battle)
- [How it works (step by step)](#how-it-works-step-by-step)
- [Installation (junior-proof)](#installation-junior-proof)
- [Configuration (every option)](#configuration-every-option)
- [Usage examples](#usage-examples)
- [Mobile / Sanctum](#mobile--sanctum)
- ["Live" testing with Mailtrap (real emails)](#live-testing-with-mailtrap-real-emails)
- [Security](#security)
- [Testing &amp; License](#testing--license)

---

What it is (and what it is NOT)
-------------------------------

[](#what-it-is-and-what-it-is-not)

**It is** the engine that lets a user sign in **without a password**: they enter their email, receive an **OTP code**, type it in, and they are in. On top of that it handles everything a serious product requires: anti-enumeration, rate-limiting, multi-tenant, audit, atomic verification, tokens for mobile apps.

**It is not** an SMS client (for SMS/WhatsApp there is `laravel-rebel-channels`), and it does **not** replace Laravel Fortify (classic login/passkey/TOTP stay with Fortify, orchestrated by `laravel-rebel-bridge-fortify`).

It depends on [`padosoft/laravel-rebel-core`](https://github.com/padosoft/laravel-rebel-core) (shared value objects/contracts). For the **big-picture view of the ecosystem**, start from the core README.

---

Quick glossary
--------------

[](#quick-glossary)

TermIn plain words**OTP**One-time code (e.g. 6 digits) sent via email.**Challenge**The "case" opened when you request a code: it has an id, an expiry and a number of attempts.**Anti-enumeration**Not letting an attacker figure out whether an email is registered or not (always the same response).**Single-use / atomic**A code works **only once**; two parallel verifications cannot both succeed.**Idempotency-Key**If the mobile app retries the same request (flaky network), you do **not** send two codes.---

Why Rebel Email-OTP — the moats
-------------------------------

[](#why-rebel-email-otp--the-moats)

★WhatIn short★**Real anti-enumeration**Response + **response time** + size identical for an existing/non-existing email. Most packages reveal whether the account exists.★**Atomic single-use verification**Pessimistic lock (or Redis Lua): no replay, no race conditions.★**Code never in plaintext**Stored as an **HMAC** with a per-challenge salt + versioned pepper (rotation without breakage).★**Web + Mobile**Same flow: web → session; mobile → **Sanctum TokenPair** (access + refresh).★**Multi-tenant &amp; audit**Per-tenant isolation + audit trail with automatic secret redaction.---

Rebel vs the others (card-battle)
---------------------------------

[](#rebel-vs-the-others-card-battle)

FeatureShopify passwordless`spatie/laravel-one-time-passwords`Generic "magic link" packages**Rebel Email-OTP**Login email→code✅✅✅✅Real anti-enumeration (msg + **timing** + size)⚠️❌❌✅Atomic single-use verification✅✅❌✅Code stored as HMAC + salt + key rotationn/a❌❌✅Idempotency-Key (mobile retry)n/a❌❌✅Single active challenge / resend with cooldown✅❌❌✅Mobile token issuance (Sanctum)n/a❌❌✅Multi-tenant + audit with redaction⚠️❌❌✅**Why it wins:** it is not an "OTP helper", it is a **product engine** with the security properties already baked in.

---

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

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

```
1) START   the user enters their email
           → Rebel opens a challenge, generates a code, sends it (queued)
           → ALWAYS responds generically (anti-enumeration) + normalized timing
2) VERIFY  the user types the code
           → ATOMIC verification (lock): expired? consumed? too many attempts?
           → if correct: challenge "consumed" (single-use) → login
                web    = session + cookie
                mobile = Sanctum TokenPair (access + refresh)
3) RESEND  (optional) resends with a cooldown and a maximum limit

```

---

Installation (junior-proof)
---------------------------

[](#installation-junior-proof)

**1. Require the package**

```
composer require padosoft/laravel-rebel-email-otp
```

**2. Publish the config and views (optional)**

```
php artisan vendor:publish --tag=rebel-email-otp-config
php artisan vendor:publish --tag=rebel-email-otp-views     # customize the screens
php artisan vendor:publish --tag=rebel-email-otp-assets    # publish the JS to public/vendor/...
```

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

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

**4. Run the migrations**

```
php artisan migrate
```

**5. Configure a mailer** (in production your own SMTP/ESP; in development/testing Mailtrap, see below).

Done: go to `/account/login` (reference routes included) and try the flow. To use **your own** controllers, disable the routes with `REBEL_OTP_ROUTES=false`.

---

Configuration (every option)
----------------------------

[](#configuration-every-option)

File: `config/rebel-email-otp.php`

KeyDefaultWhat it does`digits``6`Code digits (use `8` for high-assurance actions).`ttl_seconds``600`Code validity (NIST max: 600s = 10 min).`max_attempts``5`Verification attempts before blocking.`max_resends``3`Maximum resends.`resend_cooldown_seconds``30`Minimum wait between two resends.`store``database``database` (lock) or `redis` (Lua) for atomic verification.`timing_target_ms``250`Time target for the `start` response (anti-timing). `0` = disabled.`routes.enabled``true`Loads the reference web routes.`routes.prefix``account/login`Route prefix.---

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

[](#usage-examples)

**Start + Verify (PHP API)**

```
use Padosoft\Rebel\Core\Context\SecurityContext;
use Padosoft\Rebel\Core\Contracts\KeyedHasher;
use Padosoft\Rebel\Core\Identifiers\EmailIdentifier;
use Padosoft\Rebel\EmailOtp\RebelEmailOtp;

$otp = app(RebelEmailOtp::class);
$ctx = SecurityContext::fromRequest($request, app(KeyedHasher::class))->withGuard('customers');

// 1) start (generic response: does not reveal whether the account exists)
$start = $otp->start(EmailIdentifier::from($request->input('email')), 'customer-login', $ctx);

// 2) verify
$result = $otp->verify($start->challengeId, $request->input('code'), $ctx);

if ($result->success) {
    auth('customers')->login($result->subject); // web (mobile: see below)
}
```

**Resend with cooldown**

```
$resend = $otp->resend(EmailIdentifier::from($email), 'customer-login', $ctx);
// $resend->status === 'cooldown' | 'max_resends' | 'ok'
```

**Idempotency (mobile retry without double sending)**

```
$otp->start($identifier, 'customer-login', $ctx, idempotencyKey: $request->header('Idempotency-Key'));
```

**Resolving the user (your app)**

```
use Padosoft\Rebel\Core\Contracts\SubjectResolver;

app()->bind(SubjectResolver::class, MyCustomerResolver::class); // email → customer
// so $result->subject will be your user after verification
```

---

Mobile / Sanctum
----------------

[](#mobile--sanctum)

For headless/mobile clients, after `verify()` issue the token pair with your `TokenIssuer` (a Sanctum extension):

```
if ($result->success && $result->subject !== null) {
    $tokens = app(\Padosoft\Rebel\Core\Contracts\TokenIssuer::class)->issue($result->subject, $ctx);
    return response()->json([
        'access_token'  => $tokens->accessToken,
        'refresh_token' => $tokens->refreshToken,
        'expires_in'    => $tokens->expiresIn,
    ]);
}
```

---

"Live" testing with Mailtrap (real emails)
------------------------------------------

[](#live-testing-with-mailtrap-real-emails)

To verify that emails actually arrive (even in CI), use **[Mailtrap](https://mailtrap.io)** (free):

1. Create an account → **Email Testing → Inbox** → copy the **SMTP** credentials (or the API key).
2. Put them in `.env` (see `.env.example`): `MAILTRAP_SMTP_*` / `MAILTRAP_APIKEY` / `MAILTRAP_INBOXID`.
3. The tests in the `live` group will hit the real inbox; without credentials they **auto-skip** (offline-safe). In CI the credentials go into the **GitHub Actions secrets**.

> `.env.example` contains **all** the documented variables.

---

Security
--------

[](#security)

- Code **never in plaintext**: `HMAC(challengeId | code | salt, pepper[version])`, constant-time comparison.
- **Single-use** + atomic verification (lock / Redis Lua).
- **Anti-enumeration**: identical message, **timing** and response size.
- Rate-limiting (attempts/resends) + cooldown; idempotency.
- Audit with automatic **redaction** (never OTP/secrets in the logs).
- For high assurance prefer **passkey/step-up** (email-OTP is AAL1, see `laravel-rebel-step-up`).

---

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

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

This package ships **AI batteries** — so you (and your AI agent) can extend it correctly on the first try:

- **`CLAUDE.md`** — a concise AI working guide (purpose, conventions, architecture, how to extend, Definition of Done). Plain Markdown, so Claude Code, Cursor, Copilot and Codex all read it.
- **`AGENTS.md`** — the agent/workflow contract (branch → PR → CI → tag/release, the gates).
- **`.claude/skills/`** — invocable skills (at least `rebel-package-dev`) encoding the suite's TDD loop, the **PHPStan-level-max** recipes, the security/telemetry rules, and the release discipline.

Open the repo in your AI editor and just start — the rules, guardrails and extension recipes come with it. PRs that follow the shipped `CLAUDE.md` pass CI (PHPStan max + Pest + Pint) and review the first time around.

---

Testing &amp; License
---------------------

[](#testing--license)

```
composer test     # Pest
composer phpstan  # max level
composer pint     # style
```

MIT — see [LICENSE](LICENSE). © Padosoft.

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance91

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity40

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

52d 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 (5 commits)")

---

Tags

laravelotpAuthenticationemailloginPasswordlesspadosoftRebel

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

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

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

###  Alternatives

[defstudio/telegraph

A laravel facade to interact with Telegram Bots

813336.8k3](/packages/defstudio-telegraph)[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)[maize-tech/laravel-magic-login

Laravel Magic Login

18210.6k](/packages/maize-tech-laravel-magic-login)[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)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5022.6k](/packages/simplestats-io-laravel-client)

PHPackages © 2026

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