laravel-otp maintained by elkomy
Laravel OTP (elkomy/laravel-otp)
A production-ready, secure, and queued One-Time Password (OTP) verification package for Laravel applications. Designed to handle email verification during account registration, password reset validation, login two-factor verification, and any custom verification workflow.
🇸🇦 اضغط هنا لقراءة الدليل الكامل باللغة العربية (Arabic Documentation)
📚 Detailed Documentation
- 📦 Step-by-Step Installation & Setup
- ✉️ Email Validation & Security Handling (Unregistered vs Registered Emails)
- 📝 Account Registration & Email Verification Flow
- 🔑 Password Reset Flow with OTP
- 🇸🇦 الدليل الشامل باللغة العربية
🌟 Key Features
- ⚡ Queued Email Delivery: Emails are dispatched via Laravel Queues (Redis, Database, SQS, etc.) without blocking user requests.
- 🔒 Cryptographically Secure Generation: Uses
random_int()to generate secure numeric OTP codes. - 🛡️ Secure OTP Hashing: Plaintext codes are never stored in the database; hashes are securely stored and verified.
- ⏳ Expiration Handling: Automatic expiration enforcement (configurable, default 10 minutes).
- 🚫 Brute-Force & Attempt Limits: Configurable maximum failed attempts limit before invalidating the OTP (default 5 attempts).
- 🛑 Resend Throttling: Rate limits repeated OTP generation requests for the same recipient (default 60 seconds delay).
- 🔄 Replay & Invalidation Protection: Single-use verification; previous active tokens are automatically invalidated.
- 🎯 Generic Purpose Architecture: Supports multiple workflows (
email_verification,password_reset,login, or any custom purpose). - ✨ Form Request Validation Rule: Built-in
OtpRuleto validate OTP codes directly in Laravel FormRequests. - 📦 Infrastructure Independent: Works seamlessly with any database, queue connection, and mail provider supported by Laravel.
- 📢 Events & Exceptions: Rich domain events and specific exceptions for clean error handling in controllers.
📋 Requirements
- PHP 8.2 or higher
- Laravel 10.x, 11.x, 12.x, or 13.x
🚀 Quick Step-by-Step Installation
Step 1: Install via Composer
composer require elkomy/laravel-otp
Step 2: Publish Config and Migrations
php artisan vendor:publish --provider="Elkomy\LaravelOtp\LaravelOtpServiceProvider"
Step 3: Run Database Migrations
php artisan migrate
Step 4: Run Queue Worker
php artisan queue:work --queue=default
⚙️ Configuration
The published config/otp.php file allows you to customize every aspect of OTP behavior:
return [
// Table name in your database
'table_name' => 'otps',
// Number of digits (between 4 and 10, default: 6)
'digits' => 6,
// Expiration duration in minutes (default: 10)
'expires_in_minutes' => 10,
// Maximum failed attempts before token is locked (default: 5)
'max_attempts' => 5,
// Throttling delay between resend requests in seconds (default: 60)
'resend_throttle_seconds' => 60,
// Queue configuration for sending notification emails
'queue' => [
'enabled' => true,
'connection' => env('OTP_QUEUE_CONNECTION', null),
'queue' => env('OTP_QUEUE_NAME', 'default'),
],
// Email notification settings
'mail' => [
'subject' => 'Your Verification Code',
'greeting' => 'Hello!',
'line' => 'Your one-time verification code is:',
'footer' => 'This code will expire in :minutes minutes. If you did not request this, please ignore this email.',
'view' => null, // Optional custom Blade view (e.g. 'emails.otp')
],
];
📖 Usage Examples
1. Account Registration with Email Verification
use App\Models\User;
use Elkomy\LaravelOtp\Facades\Otp;
use Illuminate\Http\Request;
// In your Registration Controller:
public function register(Request $request)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
'password' => ['required', 'string', 'min:8'],
]);
// 1. Create the user
$user = User::create([
'name' => $validated['name'],
'email' => strtolower(trim($validated['email'])),
'password' => bcrypt($validated['password']),
]);
// 2. Send the OTP verification code via queued email
Otp::send(
identifier: $user->email,
purpose: 'email_verification',
metadata: ['user_id' => $user->id]
);
return response()->json([
'message' => 'Registration successful. A verification code has been sent to your email.',
'email' => $user->email,
], 201);
}
2. Validating OTP Code in Form Requests (OtpRule)
You can validate OTP codes effortlessly using OtpRule:
namespace App\Http\Requests;
use Elkomy\LaravelOtp\Rules\OtpRule;
use Illuminate\Foundation\Http\FormRequest;
class VerifyOtpRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => ['required', 'email'],
'code' => [
'required',
'string',
'size:6',
new OtpRule(
identifier: (string) $this->input('email'),
purpose: 'email_verification',
consume: true // Automatically marks verified if valid
),
],
];
}
}
3. Handling Unregistered Emails in Password Reset
To prevent User Enumeration Attacks, always return a generic message:
use App\Models\User;
use Elkomy\LaravelOtp\Facades\Otp;
public function sendResetOtp(Request $request)
{
$request->validate(['email' => ['required', 'email']]);
$email = strtolower(trim($request->email));
// Check if registered
$user = User::where('email', $email)->first();
if ($user !== null) {
Otp::send(identifier: $user->email, purpose: 'password_reset');
}
// Always generic response
return response()->json([
'message' => 'If an account exists with this email, a verification code has been sent.',
]);
}
🛠️ API Reference
Otp Facade Methods
| Method | Description |
|---|---|
Otp::send($identifier, $purpose = 'email_verification', $metadata = null) |
Generates, hashes, stores, and queues OTP notification. |
Otp::sendPasswordReset($email, $resetUrl = null, $metadata = null) |
Generates, hashes, and queues password reset OTP with direct reset link. |
Otp::verify($identifier, $code, $purpose = 'email_verification') |
Verifies code and marks OTP as verified. |
Otp::verifyPasswordReset($email, $code) |
Verifies password reset code and marks it as verified. |
Otp::check($identifier, $code, $purpose = 'email_verification') |
Validates code without consuming it (read-only). |
Otp::createResetUrl($baseUrl, $email, $code) |
Builds a full reset URL with query parameters. |
Otp::resend($identifier, $purpose = 'email_verification', $metadata = null) |
Resends a fresh OTP while enforcing throttle limits. |
Otp::invalidate($identifier, $purpose = null) |
Expires all active OTP tokens for the identifier. |
🛑 Exceptions Reference
All package exceptions extend Elkomy\LaravelOtp\Exceptions\OtpException:
OtpNotFoundException: No active OTP found for the given identifier and purpose.OtpInvalidException: The provided code does not match.OtpExpiredException: The code has expired.OtpTooManyAttemptsException: Maximum failed verification attempts exceeded.OtpAlreadyVerifiedException: The code was already used/verified.OtpResendTooSoonException: Request throttled (access$e->secondsRemaining).
🐳 Docker Development & Testing
Run all package commands inside Docker without requiring PHP or Composer on your host:
# Build the container
docker compose build
# Install dependencies
docker compose run --rm php-cli composer install
# Run the test suite
docker compose run --rm php-cli vendor/bin/phpunit
# Format code with Laravel Pint
docker compose run --rm php-cli vendor/bin/pint
# Run static analysis with PHPStan
docker compose run --rm php-cli vendor/bin/phpstan analyse
📄 License
The MIT License (MIT). Please see LICENSE for more information.