litepie/otp

A comprehensive Laravel package for generating, signing and managing OTP codes with multiple channels support

v1.0.0 2025-08-30 04:27 UTC

This package is not auto-updated.

Last update: 2025-08-31 02:47:10 UTC


README

Tests Code Style Static Analysis Total Downloads Latest Stable Version License

A comprehensive Laravel package for generating, signing and managing OTP (One-Time Password) codes with multiple channels support.

๐Ÿ“‹ Requirements

  • PHP 8.2 or higher
  • Laravel 10.0, 11.0, or 12.0

๐Ÿš€ Features

  • โœ… Secure OTP Generation - Generate cryptographically secure OTP codes
  • โœ… Digital Signing - Sign OTP codes for enhanced security and verification
  • โœ… Multiple Delivery Channels - Email, SMS, Database, and custom channels
  • โœ… Flexible Configuration - Customizable length, format, and expiration
  • โœ… Rate Limiting - Built-in protection against abuse
  • โœ… Multiple OTP Types - Login, email verification, password reset, 2FA, etc.
  • โœ… Event System - Complete lifecycle events for monitoring and logging
  • โœ… Queue Support - Background processing for sending OTPs
  • โœ… Auto-cleanup - Automatic removal of expired OTPs
  • โœ… Laravel 12 Ready - Full compatibility with the latest Laravel versions
  • โœ… Production Ready - Thoroughly tested and optimized for production use

๐Ÿ“ฆ Installation

You can install the package via Composer:

composer require litepie/otp

Publish Configuration

Publish the configuration file:

php artisan vendor:publish --provider="Litepie\Otp\OtpServiceProvider" --tag="config"

Run Migrations

Run the migrations to create the OTPs table:

php artisan migrate

Set Up Automatic Cleanup (Optional)

Add the following to your app/Console/Kernel.php file to automatically clean up expired OTPs:

protected function schedule(Schedule $schedule)
{
    $schedule->command('otp:cleanup')->daily();
}

๐Ÿ”ง Configuration

The configuration file config/otp.php allows you to customize:

  • Default OTP Settings - Length, format, expiration, channels
  • OTP Types - Specific settings for different use cases
  • Rate Limiting - Prevent abuse with configurable limits
  • Digital Signing - Secure OTP verification
  • Channel Configuration - Email, SMS, and custom channel settings
  • Automatic Cleanup - Keep your database clean

Environment Variables

Add these to your .env file:

# OTP Signing Secret (defaults to APP_KEY)
OTP_SIGNING_SECRET=your-secret-key

# SMS Provider Configuration
OTP_SMS_PROVIDER=log  # Options: log, nexmo, twilio

# Nexmo/Vonage
NEXMO_KEY=your-nexmo-key
NEXMO_SECRET=your-nexmo-secret
NEXMO_FROM=YourApp

# Twilio
TWILIO_SID=your-twilio-sid
TWILIO_TOKEN=your-twilio-token
TWILIO_FROM=your-twilio-number

๐Ÿ“– Usage

Quick Start

use Litepie\Otp\Facades\Otp;

// Generate and send OTP
$otp = Otp::generate()
    ->for('user@example.com')
    ->type('login')
    ->send();

// Verify OTP
$isValid = Otp::verify('123456', 'user@example.com', 'login');

if ($isValid) {
    // OTP is valid, proceed with authentication
    return response()->json(['message' => 'Login successful']);
}

Advanced Usage

// Custom OTP with specific settings
$otp = Otp::generate()
    ->for('user@example.com')
    ->type('password_reset')
    ->length(8)                    // 8 digits
    ->format('alphanumeric')       // Letters and numbers
    ->expiresIn(900)              // 15 minutes
    ->via(['email', 'sms'])       // Multiple channels
    ->with(['user_id' => 123])    // Additional data
    ->send();

// Check if OTP exists before generating new one
if (!Otp::exists('user@example.com', 'login')) {
    $otp = Otp::generate()
        ->for('user@example.com')
        ->type('login')
        ->send();
}

// Invalidate existing OTP
Otp::invalidate('user@example.com', 'login');

Exception Handling

use Litepie\Otp\Exceptions\TooManyAttemptsException;
use Litepie\Otp\Exceptions\RateLimitExceededException;

try {
    $isValid = Otp::verify($code, $email, 'login');
} catch (TooManyAttemptsException $e) {
    return response()->json(['error' => 'Too many failed attempts'], 429);
} catch (RateLimitExceededException $e) {
    return response()->json(['error' => 'Rate limit exceeded'], 429);
}

๐ŸŽฏ OTP Types

The package supports multiple OTP types with individual configurations:

Built-in Types

Type Use Case Default Length Default Expiry
login User authentication 6 digits 5 minutes
email_verification Email verification 6 digits 10 minutes
password_reset Password reset 8 characters 15 minutes
two_factor 2FA authentication 6 digits 3 minutes
phone_verification Phone verification 6 digits 5 minutes

Custom Types

Define custom OTP types in your configuration:

// config/otp.php
'types' => [
    'transaction_verify' => [
        'length' => 8,
        'format' => 'alphanumeric',
        'expires_in' => 600, // 10 minutes
        'max_attempts' => 3,
        'channels' => ['email', 'sms'],
        'rate_limit' => [
            'max_attempts' => 2,
            'decay_minutes' => 30,
        ],
    ],
],

๐Ÿ“ก Delivery Channels

Email Channel

Sends OTP via email using Laravel's notification system or traditional mail.

Otp::generate()
    ->for('user@example.com')
    ->via('email')
    ->send();

SMS Channel

Send OTPs via SMS using various providers:

Otp::generate()
    ->for('+1234567890')
    ->via('sms')
    ->send();

Supported SMS Providers:

  • Log (for testing)
  • Nexmo/Vonage
  • Twilio
  • Custom providers (extensible)

Database Channel

Store OTP in database for manual retrieval:

Otp::generate()
    ->for('user@example.com')
    ->via('database')
    ->send();

// Retrieve from database
$otpRecord = \Litepie\Otp\Otp::where('identifier', 'user@example.com')
    ->where('type', 'login')
    ->valid()
    ->first();

Multiple Channels

Send via multiple channels simultaneously:

Otp::generate()
    ->for('user@example.com')
    ->via(['email', 'sms', 'database'])
    ->send();

Custom Channels

Create custom delivery channels:

use Litepie\Otp\Contracts\OtpChannelInterface;

class SlackChannel implements OtpChannelInterface
{
    public function send(string $identifier, string $code, array $data = []): bool
    {
        // Implementation for Slack delivery
        return true;
    }

    public function canHandle(string $identifier): bool
    {
        return str_starts_with($identifier, '@slack:');
    }
}

// Register the custom channel
Otp::extend('slack', function () {
    return new SlackChannel();
});

๐Ÿ“Š Events

The package fires comprehensive events for monitoring and logging:

Available Events

  • OtpGenerated - When an OTP is generated
  • OtpSent - When an OTP is sent via a channel
  • OtpVerified - When an OTP is successfully verified
  • OtpFailed - When OTP verification fails

Event Listeners

// In EventServiceProvider
protected $listen = [
    \Litepie\Otp\Events\OtpGenerated::class => [
        \App\Listeners\LogOtpGenerated::class,
    ],
    \Litepie\Otp\Events\OtpVerified::class => [
        \App\Listeners\LogOtpVerified::class,
        \App\Listeners\SendWelcomeEmail::class,
    ],
    \Litepie\Otp\Events\OtpFailed::class => [
        \App\Listeners\LogFailedOtpAttempt::class,
    ],
];

Example Listener

class LogOtpGenerated
{
    public function handle(\Litepie\Otp\Events\OtpGenerated $event)
    {
        Log::info('OTP generated', [
            'identifier' => $event->otp->identifier,
            'type' => $event->otp->type,
            'expires_at' => $event->otp->expires_at,
        ]);
    }
}

๐Ÿ› ๏ธ Artisan Commands

Cleanup Expired OTPs

# Clean up expired OTPs (default: 7 days)
php artisan otp:cleanup

# Clean up OTPs older than specific days
php artisan otp:cleanup --days=3

# Force cleanup without confirmation
php artisan otp:cleanup --force

๐Ÿ”’ Security Features

  • Digital Signing - All OTPs are digitally signed using HMAC-SHA256
  • Rate Limiting - Configurable rate limiting per identifier and type
  • Secure Generation - Cryptographically secure random code generation
  • Attempt Tracking - Track and limit verification attempts
  • Automatic Cleanup - Remove expired OTPs automatically
  • Timing Attack Protection - Use hash_equals() for secure comparisons

๐Ÿงช Testing

Running Tests

# Run all tests
composer test

# Run tests with coverage
composer test-coverage

# Run specific test
vendor/bin/phpunit tests/Unit/OtpTest.php

Test Example

use Litepie\Otp\Facades\Otp;
use Illuminate\Support\Facades\Mail;

public function test_otp_generation_and_verification()
{
    Mail::fake();

    // Generate OTP
    $otp = Otp::generate()
        ->for('test@example.com')
        ->type('login')
        ->send();

    // Verify OTP
    $this->assertTrue(
        Otp::verify($otp->code, 'test@example.com', 'login')
    );

    // Assert mail was sent
    Mail::assertSent(\Litepie\Otp\Notifications\OtpNotification::class);
}

๐Ÿ“š Documentation

๐Ÿค Contributing

We welcome contributions! Please see CONTRIBUTING.md for details.

Development Setup

git clone https://github.com/litepie/otp.git
cd otp
composer install
composer test

๐Ÿ” Security

If you discover a security vulnerability, please send an email to security@litepie.com. All security vulnerabilities will be promptly addressed.

๐Ÿ“„ License

The MIT License (MIT). Please see License File for more information.

๐Ÿ’– Support

Made with โค๏ธ by Litepie