A comprehensive Laravel package for generating, signing and managing OTP (One-Time Password) codes with multiple channels support.
- PHP 8.2 or higher
- Laravel 10.0, 11.0, or 12.0
- β 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
You can install the package via Composer:
composer require litepie/otpPublish the configuration file:
php artisan vendor:publish --provider="Litepie\Otp\OtpServiceProvider" --tag="config"Run the migrations to create the OTPs table:
php artisan migrateAdd 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();
}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
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-numberuse Litepie\Otp\Facades\Otp;
// Generate and send OTP
$otp = Otp::generate()
->for('[email protected]')
->type('login')
->send();
// Verify OTP
$isValid = Otp::verify('123456', '[email protected]', 'login');
if ($isValid) {
// OTP is valid, proceed with authentication
return response()->json(['message' => 'Login successful']);
}// Custom OTP with specific settings
$otp = Otp::generate()
->for('[email protected]')
->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('[email protected]', 'login')) {
$otp = Otp::generate()
->for('[email protected]')
->type('login')
->send();
}
// Invalidate existing OTP
Otp::invalidate('[email protected]', 'login');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);
}The package supports multiple OTP types with individual configurations:
| 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 |
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,
],
],
],Sends OTP via email using Laravel's notification system or traditional mail.
Otp::generate()
->for('[email protected]')
->via('email')
->send();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)
Store OTP in database for manual retrieval:
Otp::generate()
->for('[email protected]')
->via('database')
->send();
// Retrieve from database
$otpRecord = \Litepie\Otp\Otp::where('identifier', '[email protected]')
->where('type', 'login')
->valid()
->first();Send via multiple channels simultaneously:
Otp::generate()
->for('[email protected]')
->via(['email', 'sms', 'database'])
->send();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();
});The package fires comprehensive events for monitoring and logging:
OtpGenerated- When an OTP is generatedOtpSent- When an OTP is sent via a channelOtpVerified- When an OTP is successfully verifiedOtpFailed- When OTP verification fails
// 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,
],
];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,
]);
}
}# 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- 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
# Run all tests
composer test
# Run tests with coverage
composer test-coverage
# Run specific test
vendor/bin/phpunit tests/Unit/OtpTest.phpuse Litepie\Otp\Facades\Otp;
use Illuminate\Support\Facades\Mail;
public function test_otp_generation_and_verification()
{
Mail::fake();
// Generate OTP
$otp = Otp::generate()
->for('[email protected]')
->type('login')
->send();
// Verify OTP
$this->assertTrue(
Otp::verify($otp->code, '[email protected]', 'login')
);
// Assert mail was sent
Mail::assertSent(\Litepie\Otp\Notifications\OtpNotification::class);
}- Examples - Comprehensive usage examples
- Contributing - How to contribute
- Security - Security policy
- Changelog - Version history
We welcome contributions! Please see CONTRIBUTING.md for details.
git clone https://github.com/litepie/otp.git
cd otp
composer install
composer testIf you discover a security vulnerability, please send an email to [email protected]. All security vulnerabilities will be promptly addressed.
The MIT License (MIT). Please see License File for more information.
- β Star this repo if you find it helpful
- π Report issues on GitHub Issues
- π‘ Request features via GitHub Discussions
- π§ Contact us at [email protected]
Made with β€οΈ by Litepie