elkomy/laravel-otp

Secure, queued, and configurable OTP verification package for Laravel applications.

Maintainers

Package info

github.com/drelkomy/laravel-otp

pkg:composer/elkomy/laravel-otp

Transparency log

Statistics

Installs: 23

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.1 2026-08-31 19:49 UTC

This package is auto-updated.

Last update: 2026-08-31 19:56:09 UTC


README

Latest Version on Packagist Total Downloads License

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

🌟 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 OtpRule to 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.