addicta / otp
Multi-provider OTP package for Laravel
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/addicta/otp
Requires
- php: ^8.2|^8.3|^8.4
- guzzlehttp/guzzle: *
- illuminate/support: ^10.0|^11.0|^12.0
- twilio/sdk: ^8.4
Requires (Dev)
- orchestra/testbench: ^8.0
- phpunit/phpunit: ^10.0
This package is not auto-updated.
Last update: 2025-12-13 19:23:01 UTC
README
A comprehensive Laravel package for sending and verifying One-Time Passwords (OTP) via SMS using multiple providers including Twilio and Unifonic.
Features
- ๐ก Multi-Channel Support: Send OTPs via Phone (SMS) or Email
- ๐ Multi-Provider Support: Twilio (SMS & Verify API) and Unifonic providers
- โจ Twilio Verify Service: Full support for Twilio's Verify API with custom OTP codes
- ๐ง Beautiful Email Templates: Professional, responsive HTML email templates
- ๐ก๏ธ Security Features: Rate limiting, attempt tracking, and automatic blocking
- โฐ Configurable Expiry: Customizable OTP expiration times
- ๐ Resend Protection: Prevents spam with configurable resend delays
- ๐งช Fully Tested: Comprehensive test suite with 16 tests
- ๐ฏ Laravel Integration: Service provider, facades, and auto-discovery
- ๐ฑ Easy to Use: Simple API for generating and verifying OTPs
- ๐ Multi-Language: Built-in English and Arabic translations for both SMS and Email
Installation
Via Composer
composer require one-studio/otp
Laravel Auto-Discovery
The package will be automatically discovered by Laravel. If you're using Laravel 5.5+, no additional configuration is required.
Manual Registration (Laravel < 5.5)
Add the service provider to your config/app.php:
'providers' => [ // ... Addicta\Otp\OtpServiceProvider::class, ],
Add the facade alias:
'aliases' => [ // ... 'Otp' => Addicta\Otp\Facades\Otp::class, ],
Configuration
Publishing Configuration and Translations
Publish the configuration file:
php artisan vendor:publish --provider="Addicta\Otp\OtpServiceProvider" --tag="otp-config"
Publish the translation files:
php artisan vendor:publish --provider="Addicta\Otp\OtpServiceProvider" --tag="otp-translations"
This will create:
config/otp.php- Configuration filelang/vendor/otp/en/otp.php- English translationslang/vendor/otp/ar/otp.php- Arabic translations
Environment Variables
Add the following environment variables to your .env file:
# Default Channel: 'phone' or 'email' (default: phone) OTP_DEFAULT_CHANNEL=phone # OTP Provider for phone channel (twilio or unifonic) OTP_PROVIDER=twilio # Twilio Configuration TWILIO_ACCOUNT_SID=your_twilio_account_sid TWILIO_AUTH_TOKEN=your_twilio_auth_token # Twilio Service Type: 'sms' for direct SMS or 'verify' for Twilio Verify API TWILIO_SERVICE_TYPE=sms # Required for SMS service type TWILIO_FROM=your_twilio_phone_number # Required for Verify service type TWILIO_VERIFICATION_SID=your_twilio_verification_sid # Unifonic Configuration (if using Unifonic) UNIFONIC_APP_SID=your_unifonic_app_sid UNIFONIC_SENDER_ID=your_unifonic_sender_id # Email Configuration OTP_EMAIL_FROM_ADDRESS=noreply@yourapp.com OTP_EMAIL_FROM_NAME="Your App Name" OTP_EMAIL_SUBJECT="Your Verification Code" # OTP Settings OTP_LENGTH=4 OTP_EXPIRY=5 MAX_ATTEMPTS=3 RESEND_DELAY=60 BLOCK_DURATION=30 # Test Mode Settings OTP_TEST_MODE=false OTP_TEST_CODE=8888
Configuration File
The published configuration file (config/otp.php) contains:
return [ // Default channel for OTP delivery: 'phone' or 'email' 'default_channel' => env('OTP_DEFAULT_CHANNEL', 'phone'), // Default provider for phone OTPs 'default' => env('OTP_PROVIDER', 'twilio'), // SMS/Phone providers 'providers' => [ 'twilio' => [ 'driver' => 'twilio', 'account_sid' => env('TWILIO_ACCOUNT_SID'), 'auth_token' => env('TWILIO_AUTH_TOKEN'), 'service_type' => env('TWILIO_SERVICE_TYPE', 'sms'), // 'sms' or 'verify' 'verification_sid' => env('TWILIO_VERIFICATION_SID'), // Required for 'verify' service 'from' => env('TWILIO_FROM'), // Required for 'sms' service ], 'unifonic' => [ 'driver' => 'unifonic', 'app_sid' => env('UNIFONIC_APP_SID'), 'sender_id' => env('UNIFONIC_SENDER_ID'), ], ], // Email provider configuration 'email' => [ 'driver' => 'email', 'from_address' => env('OTP_EMAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS', 'noreply@example.com')), 'from_name' => env('OTP_EMAIL_FROM_NAME', env('MAIL_FROM_NAME', 'OTP Service')), 'subject' => env('OTP_EMAIL_SUBJECT', 'Your Verification Code'), ], 'otp_length' => env('OTP_LENGTH', 4), 'otp_expiry' => env('OTP_EXPIRY', 5), // minutes 'max_attempts' => env('MAX_ATTEMPTS', 3), 'resend_delay' => env('RESEND_DELAY', 60), // seconds 'block_duration' => env('BLOCK_DURATION', 30), // minutes after max attempts // Rate limiting configuration 'rate_limit' => [ 'enabled' => env('OTP_RATE_LIMIT_ENABLED', true), 'max_requests_per_hour' => env('OTP_MAX_REQUESTS_PER_HOUR', 3), // per phone number or email 'block_duration' => env('OTP_BLOCK_DURATION', 60), // minutes to block after limit exceeded ], // Test mode configuration 'test_mode' => env('OTP_TEST_MODE', false), 'test_otp' => env('OTP_TEST_CODE', '8888'), 'test_numbers' => [ '+1234567890', '+9876543210', // Add more test numbers as needed ], 'test_emails' => [ 'test@example.com', 'demo@example.com', // Add more test emails as needed ], ];
Usage
Multi-Channel Support (Phone & Email)
The package supports sending OTPs through two channels: Phone (SMS) and Email. By default, OTPs are sent via phone, but you can easily switch channels.
Default Channel Configuration
Set your preferred default channel in .env:
OTP_DEFAULT_CHANNEL=phone # or 'email'
Sending OTP via Phone (Default)
use Addicta\Otp\Facades\Otp; // Send OTP to phone number (uses default channel: phone) $result = Otp::generate('+1234567890'); // Or explicitly specify phone channel $result = Otp::generate('+1234567890', 'phone'); if ($result['success']) { echo "OTP sent via SMS!"; echo "Channel: " . $result['channel']; // 'phone' }
Sending OTP via Email
use Addicta\Otp\Facades\Otp; // Send OTP to email address $result = Otp::generate('user@example.com', 'email'); if ($result['success']) { echo "OTP sent via email!"; echo "Channel: " . $result['channel']; // 'email' }
Verifying OTP (Works for Both Channels)
// Verification works the same way for both channels $phone = '+1234567890'; $verifyResult = Otp::verify($phone, '1234'); // Or for email $email = 'user@example.com'; $verifyResult = Otp::verify($email, '1234'); if ($verifyResult['success']) { echo "Verified successfully!"; }
Dynamic Channel Selection
You can let users choose their preferred verification method:
public function sendOtp(Request $request) { $recipient = $request->input('recipient'); // phone or email $channel = $request->input('channel', 'phone'); // default to phone $result = Otp::generate($recipient, $channel); return response()->json($result); }
Channel-Specific Features
Phone Channel:
- Uses configured SMS provider (Twilio, Unifonic)
- Supports Twilio Verify API
- Custom message templates
- Multilingual SMS messages
Email Channel:
- Beautiful HTML email templates
- Plain text fallback
- Customizable sender information
- Responsive design
- Security warnings included
Email Template Customization
Publish the email views to customize them:
php artisan vendor:publish --provider="Addicta\Otp\OtpServiceProvider" --tag="otp-views"
This creates:
resources/views/vendor/otp/emails/otp.blade.php- HTML email templateresources/views/vendor/otp/emails/otp-text.blade.php- Plain text template
Response Format
Both channels return the same response structure:
[
'success' => true,
'message' => 'OTP sent successfully.',
'remaining_time' => 60,
'type' => 'success',
'channel' => 'email' // or 'phone'
]
Multi-Language Support
The package supports multiple languages with built-in English and Arabic translations. You can customize messages by publishing and editing the translation files.
Available Languages:
- English (
en) - Arabic (
ar)
Setting Application Locale:
In your Laravel application, set the locale in config/app.php:
'locale' => 'ar', // For Arabic 'locale' => 'en', // For English
Or dynamically in your application:
App::setLocale('ar'); // Switch to Arabic App::setLocale('en'); // Switch to English
Customizing Messages:
After publishing translations, you can customize the messages in:
lang/vendor/otp/en/otp.php- English messageslang/vendor/otp/ar/otp.php- Arabic messages
Rate Limiting
The package includes built-in rate limiting to prevent abuse and excessive OTP requests. Rate limiting uses a rolling window approach - tracking requests in the last 60 minutes per phone number with automatic blocking.
Configuration
Rate limiting can be configured in your .env file:
# Enable/disable rate limiting OTP_RATE_LIMIT_ENABLED=true # Maximum requests per phone number per hour OTP_MAX_REQUESTS_PER_HOUR=3 # Block duration in minutes after limit exceeded OTP_BLOCK_DURATION=60
How It Works
- Per Phone Number: Rate limits are applied individually to each phone number
- Rolling Window: Tracks requests in the last 60 minutes (not fixed hourly blocks)
- Automatic Blocking: When limit is exceeded, phone is blocked for specified duration
- Block Duration: Configurable block time (default: 60 minutes)
- Cache-Based: Uses Laravel's cache system for tracking with automatic cleanup
Rate Limit Response
When rate limits are exceeded, the service returns:
[
'success' => false,
'message' => 'Rate limit exceeded. Maximum 3 requests per hour allowed. Blocked for 60 minutes.',
'remaining_time' => 3600, // seconds until unblocked
'type' => 'rate_limited'
]
When phone is blocked, the service returns:
[
'success' => false,
'message' => 'Phone number is blocked due to rate limiting. Try again in 45 minutes.',
'remaining_time' => 2700, // seconds remaining
'type' => 'rate_limited'
]
Response Types
The type field indicates the reason for the response:
success- OTP sent successfullyrate_limited- Rate limit exceeded or phone blockedblocked- Phone blocked due to failed attemptsresend_delay- Resend delay activesend_failed- Failed to send OTP
Disabling Rate Limiting
To disable rate limiting entirely:
OTP_RATE_LIMIT_ENABLED=false
Use Cases
- Production: Enable rate limiting to prevent abuse
- Development: Disable rate limiting for easier testing
- High Traffic: Adjust limits based on your application needs
Test Mode
The package includes a built-in test mode for development and testing purposes. This allows you to test OTP functionality without sending actual SMS messages.
Enabling Test Mode:
- Global Test Mode - Enable for all phone numbers:
OTP_TEST_MODE=true OTP_TEST_CODE=8888 # Rate limiting configuration OTP_RATE_LIMIT_ENABLED=true OTP_MAX_REQUESTS_PER_HOUR=3 OTP_BLOCK_DURATION=60
- Specific Test Numbers - Add phone numbers to test list:
// In config/otp.php 'test_numbers' => [ '+1234567890', '+9876543210', '+201120305686', // Your test number ],
Test Mode Behavior:
- No SMS Sent: When test mode is active, no actual SMS is sent
- Fixed OTP: Uses the configured test OTP code (default: 8888)
- Same Verification: Test OTPs work exactly like real OTPs
- Response Indicators: Test mode responses use the same format as regular responses
Test Mode Response Example:
[
'success' => true,
'message' => 'OTP sent successfully.',
'remaining_time' => 60,
'type' => 'success'
]
Use Cases:
- Development and testing
- Demo environments
- CI/CD pipelines
- Unit testing
- Avoiding SMS costs during development
Using the Facade
use Addicta\Otp\Facades\Otp; // Generate OTP $result = Otp::generate('+1234567890'); if ($result['success']) { echo "OTP sent successfully!"; echo "Expires in: " . $result['expires_in'] . " seconds"; } else { echo "Failed to send OTP: " . $result['message']; } // Verify OTP $verifyResult = Otp::verify('+1234567890', '1234'); if ($verifyResult['success']) { echo "OTP verified successfully!"; } else { echo "Verification failed: " . $verifyResult['message']; if (isset($verifyResult['remaining_attempts'])) { echo "Remaining attempts: " . $verifyResult['remaining_attempts']; } }
Using Dependency Injection
use Addicta\Otp\OtpService; class AuthController extends Controller { protected $otpService; f public function __construct(OtpService $otpService) { $this->otpService = $otpService; } public function sendOtp(Request $request) { $phone = $request->input('phone'); $result = $this->otpService->generate($phone); return response()->json($result); } f public function verifyOtp(Request $request) { $phone = $request->input('phone'); $code = $request->input('code'); $result = $this->otpService->verify($phone, $code); return response()->json($result); } }
Using the Manager Directly
use Addicta\Otp\OtpManager; $manager = app(OtpManager::class); $provider = $manager->driver(); // Gets the default provider $result = $provider->send('+1234567890', 'Your OTP is: 1234');
Twilio Verify Service Usage
When using Twilio Verify service, the package handles OTP generation and verification seamlessly:
Sending OTP with Verify Service
use Addicta\Otp\Facades\Otp; // Configure .env for Verify service // TWILIO_SERVICE_TYPE=verify // TWILIO_VERIFICATION_SID=VAxxxxxxxxxxxxx // Generate and send OTP using Verify API $result = Otp::generate('+1234567890'); if ($result['success']) { // OTP sent via Twilio Verify with custom code echo "Verification code sent!"; echo "User will receive SMS from Twilio Verify service"; } else { echo "Error: " . $result['message']; }
Verifying OTP with Verify Service
// The package handles verification automatically $verifyResult = Otp::verify('+1234567890', '1234'); if ($verifyResult['success']) { // OTP verified successfully // User can proceed with registration/login echo "Phone number verified!"; } else { echo "Invalid code: " . $verifyResult['message']; }
Switching Between SMS and Verify
You can easily switch between service types without changing your application code:
For SMS Service:
TWILIO_SERVICE_TYPE=sms TWILIO_FROM=+1234567890
For Verify Service:
TWILIO_SERVICE_TYPE=verify TWILIO_VERIFICATION_SID=VAxxxxxxxxxxxxx
Your application code remains the same regardless of which service you use!
API Reference
OtpService Methods
generate(string $phone): array
Generates and sends an OTP to the specified phone number.
Parameters:
$phone(string): Phone number in international format (e.g., +1234567890)
Returns:
[
'success' => bool,
'message' => string,
'expires_in' => int, // seconds
'remaining_time' => int, // if resend delay active
'blocked_until' => Carbon, // if blocked
]
verify(string $phone, string $code): array
Verifies an OTP code for the specified phone number.
Parameters:
$phone(string): Phone number in international format$code(string): OTP code to verify
Returns:
[
'success' => bool,
'message' => string,
'remaining_attempts' => int, // if verification failed
]
Security Features
Rate Limiting
- Resend Delay: Prevents immediate resending of OTPs (default: 60 seconds)
- Max Attempts: Limits verification attempts (default: 3 attempts)
- Block Duration: Temporary blocking after max attempts exceeded (default: 30 minutes)
OTP Management
- Expiry: OTPs expire after configured time (default: 5 minutes)
- Single Use: OTPs are automatically removed after successful verification
- Cache Storage: OTPs are stored securely in Laravel's cache system
Error Handling
The package returns detailed error messages for various scenarios. All messages are translatable and will be returned in the current application locale:
English Messages:
// Rate limiting [ 'success' => false, 'message' => 'Please wait 45 seconds before requesting a new OTP.', 'remaining_time' => 45 ] // Blocked phone [ 'success' => false, 'message' => 'Too many attempts. Please try again later.', 'blocked_until' => Carbon::now()->addMinutes(30) ] // Invalid OTP [ 'success' => false, 'message' => 'Invalid OTP.', 'remaining_attempts' => 2 ] // Expired OTP [ 'success' => false, 'message' => 'OTP expired or not found.' ]
Arabic Messages (when locale is set to 'ar'):
// Rate limiting [ 'success' => false, 'message' => 'ูุฑุฌู ุงูุงูุชุธุงุฑ 45 ุซุงููุฉ ูุจู ุทูุจ ุฑู ุฒ ุชุญูู ุฌุฏูุฏ.', 'remaining_time' => 45 ] // Blocked phone [ 'success' => false, 'message' => 'ู ุญุงููุงุช ูุซูุฑุฉ ุฌุฏุงู. ูุฑุฌู ุงูู ุญุงููุฉ ู ุฑุฉ ุฃุฎุฑู ูุงุญูุงู.', 'blocked_until' => Carbon::now()->addMinutes(30) ] // Invalid OTP [ 'success' => false, 'message' => 'ุฑู ุฒ ุงูุชุญูู ุบูุฑ ุตุญูุญ.', 'remaining_attempts' => 2 ] // Expired OTP [ 'success' => false, 'message' => 'ุงูุชูุช ุตูุงุญูุฉ ุฑู ุฒ ุงูุชุญูู ุฃู ูู ูุชู ุงูุนุซูุฑ ุนููู.' ]
Testing
The package includes comprehensive tests. Run them using:
# Run all tests ./vendor/bin/phpunit # Run specific test suites ./vendor/bin/phpunit --testsuite=Unit ./vendor/bin/phpunit --testsuite=Feature # Run with coverage ./vendor/bin/phpunit --coverage-html coverage
Supported Providers
Twilio
The package supports two Twilio service types:
1. SMS Service (Direct Messaging)
- Service Type:
sms - Best For: Simple OTP delivery with custom message formatting
- Required Config:
account_sid,auth_token,from - Features:
- Full control over message content
- Custom message templates with multilingual support
- Direct SMS delivery
- Lower cost per message
Configuration Example:
TWILIO_SERVICE_TYPE=sms TWILIO_ACCOUNT_SID=your_account_sid TWILIO_AUTH_TOKEN=your_auth_token TWILIO_FROM=+1234567890
2. Verify API Service (Recommended)
- Service Type:
verify - Best For: Enhanced security and compliance features
- Required Config:
account_sid,auth_token,verification_sid - Features:
- Built-in fraud detection
- Automatic rate limiting and abuse prevention
- Carrier-level integrations for better delivery
- Custom OTP code support
- Geographic and carrier analytics
- Compliance with regulatory requirements
Configuration Example:
TWILIO_SERVICE_TYPE=verify TWILIO_ACCOUNT_SID=your_account_sid TWILIO_AUTH_TOKEN=your_auth_token TWILIO_VERIFICATION_SID=your_verification_sid
Getting Your Verification SID:
- Log in to your Twilio Console
- Navigate to Verify โ Services
- Create a new Verify Service or select an existing one
- Copy the Service SID (starts with VA...)
Key Differences:
| Feature | SMS Service | Verify Service |
|---|---|---|
| Message Control | Full customization | Template-based |
| Fraud Detection | Manual | Built-in |
| Delivery Optimization | Standard | Carrier-optimized |
| Analytics | Basic | Advanced |
| Compliance | Manual | Automatic |
| Setup Complexity | Simple | Moderate |
| Cost | Lower | Higher |
Choosing the Right Service:
- Use SMS for: Simple applications, full message control, budget-conscious projects
- Use Verify for: Production applications, enhanced security, compliance requirements
Documentation:
Unifonic
- Driver:
unifonic - Required Config:
app_sid,sender_id - Documentation: Unifonic SMS API
Email Provider
The package includes a built-in email provider for sending OTPs via email.
Configuration
The email provider uses Laravel's mail system, so ensure you have your mail driver configured in config/mail.php or .env:
MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=your_username MAIL_PASSWORD=your_password MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=noreply@yourapp.com MAIL_FROM_NAME="${APP_NAME}"
Features
- Beautiful Templates: Professional, responsive HTML email design
- Plain Text Fallback: Automatic plain text version for email clients
- Customizable: Publish and modify email templates to match your brand
- Security Warnings: Built-in security notices to prevent phishing
- Multi-Language: Supports all translated languages (English, Arabic, etc.)
Email Template Preview
The email includes:
- Clear verification code display
- Expiration time notice
- Security warnings
- Professional styling
- Responsive design for mobile devices
Supported Mail Drivers
Works with all Laravel mail drivers:
- SMTP
- Mailgun
- Postmark
- Amazon SES
- Sendmail
- Log (for testing)
Requirements
- PHP 8.2+
- Laravel 10.0+
- Twilio SDK (for Twilio provider)
- Guzzle HTTP (for Unifonic provider)
License
This package is open-sourced software licensed under the MIT license.
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Support
For support, please open an issue on the GitHub repository or contact the maintainer.
Changelog
Version 0.9.0 (Current)
- ๐ Multi-Channel Support: Major feature - Send OTPs via Phone (SMS) or Email
- ๐ง Email OTP Provider: Complete email provider implementation with beautiful HTML templates
- ๐จ Professional Email Templates: Responsive HTML and plain text email templates
- ๐ Channel-Aware Translations: Email-specific messages in English and Arabic
- โ๏ธ Configurable Default Channel: Set default channel via environment variable (phone or email)
- ๐งช Test Mode for Email: Support for test emails alongside test phone numbers
- ๐ Unified API: Same API for both channels - just change the recipient and channel parameter
- ๐ Backward Compatible: Existing phone-only implementations continue to work without changes
Version 0.8.0
- Twilio Verify Service: Added full support for Twilio Verify API alongside traditional SMS service
- Dual Service Types: Choose between 'sms' (direct messaging) or 'verify' (Twilio Verify API)
- Custom OTP with Verify: Support for custom OTP codes in Twilio Verify service
- Service-Specific Configuration: Automatic configuration validation based on selected service type
- Enhanced Documentation: Comprehensive guide for choosing and configuring Twilio services
- Improved Code Structure: Refactored provider with constants and better separation of concerns
Version 0.7.0
- Rate Limiting: Implemented configurable rate limiting per phone number with rolling window approach
- Rolling Window: Tracks requests in the last 60 minutes (not fixed hourly blocks) to prevent bypassing limits
- Unified Response Format: Simplified all responses to use consistent structure with
success,message,remaining_time, andtypefields - Response Type Enum: Added
OtpResponseTypeenum for type-safe response handling - Seconds-Based Timing: All timing fields now use seconds for easier frontend integration
- Enhanced Security: Prevents users from bypassing rate limits by waiting for hour changes
- Improved Testing: Added comprehensive tests for rolling window rate limiting
Version 0.1
- Initial release
- Twilio and Unifonic provider support
- Rate limiting per phone number (hourly and daily limits)
- Configurable rate limits via environment variables
- Rate limit responses with retry information
- Comprehensive test suite (26 tests)
- Laravel auto-discovery support
- Multi-language support (English & Arabic)
- Translatable messages for all responses
- Publishable translation files for customization
- Test Mode for development and testing
- Test phone numbers support
- Fixed test OTP for consistent testing