Addicta OTP Package
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 file
lang/vendor/otp/en/otp.php - English translations
lang/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' => env('OTP_DEFAULT_CHANNEL', 'phone'),
'default' => env('OTP_PROVIDER', 'twilio'),
'providers' => [
'twilio' => [
'driver' => 'twilio',
'account_sid' => env('TWILIO_ACCOUNT_SID'),
'auth_token' => env('TWILIO_AUTH_TOKEN'),
'service_type' => env('TWILIO_SERVICE_TYPE', 'sms'),
'verification_sid' => env('TWILIO_VERIFICATION_SID'),
'from' => env('TWILIO_FROM'),
],
'unifonic' => [
'driver' => 'unifonic',
'app_sid' => env('UNIFONIC_APP_SID'),
'sender_id' => env('UNIFONIC_SENDER_ID'),
],
],
'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),
'max_attempts' => env('MAX_ATTEMPTS', 3),
'resend_delay' => env('RESEND_DELAY', 60),
'block_duration' => env('BLOCK_DURATION', 30),
'rate_limit' => [
'enabled' => env('OTP_RATE_LIMIT_ENABLED', true),
'max_requests_per_hour' => env('OTP_MAX_REQUESTS_PER_HOUR', 3),
'block_duration' => env('OTP_BLOCK_DURATION', 60),
],
'test_mode' => env('OTP_TEST_MODE', false),
'test_otp' => env('OTP_TEST_CODE', '8888'),
'test_numbers' => [
'+1234567890',
'+9876543210',
],
'test_emails' => [
'test@example.com',
'demo@example.com',
],
];
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;
$result = Otp::generate('+1234567890');
$result = Otp::generate('+1234567890', 'phone');
if ($result['success']) {
echo "OTP sent via SMS!";
echo "Channel: " . $result['channel'];
}
Sending OTP via Email
use Addicta\Otp\Facades\Otp;
$result = Otp::generate('user@example.com', 'email');
if ($result['success']) {
echo "OTP sent via email!";
echo "Channel: " . $result['channel'];
}
Verifying OTP (Works for Both Channels)
$phone = '+1234567890';
$verifyResult = Otp::verify($phone, '1234');
$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');
$channel = $request->input('channel', '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 template
resources/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'
]
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:
Setting Application Locale:
In your Laravel application, set the locale in config/app.php:
'locale' => 'ar',
'locale' => 'en',
Or dynamically in your application:
App::setLocale('ar');
App::setLocale('en');
Customizing Messages:
After publishing translations, you can customize the messages in:
lang/vendor/otp/en/otp.php - English messages
lang/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,
'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,
'type' => 'rate_limited'
]
Response Types
The type field indicates the reason for the response:
success - OTP sent successfully
rate_limited - Rate limit exceeded or phone blocked
blocked - Phone blocked due to failed attempts
resend_delay - Resend delay active
send_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:
'test_numbers' => [
'+1234567890',
'+9876543210',
'+201120305686',
],
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;
$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'];
}
$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();
$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;
$result = Otp::generate('+1234567890');
if ($result['success']) {
echo "Verification code sent!";
echo "User will receive SMS from Twilio Verify service";
} else {
echo "Error: " . $result['message'];
}
Verifying OTP with Verify Service
$verifyResult = Otp::verify('+1234567890', '1234');
if ($verifyResult['success']) {
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,
'remaining_time' => int,
'blocked_until' => Carbon,
]
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,
]
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:
[
'success' => false,
'message' => 'Please wait 45 seconds before requesting a new OTP.',
'remaining_time' => 45
]
[
'success' => false,
'message' => 'Too many attempts. Please try again later.',
'blocked_until' => Carbon::now()->addMinutes(30)
]
[
'success' => false,
'message' => 'Invalid OTP.',
'remaining_attempts' => 2
]
[
'success' => false,
'message' => 'OTP expired or not found.'
]
Arabic Messages (when locale is set to 'ar'):
[
'success' => false,
'message' => 'يرجى الانتظار 45 ثانية قبل طلب رمز تحقق جديد.',
'remaining_time' => 45
]
[
'success' => false,
'message' => 'محاولات كثيرة جداً. يرجى المحاولة مرة أخرى لاحقاً.',
'blocked_until' => Carbon::now()->addMinutes(30)
]
[
'success' => false,
'message' => 'رمز التحقق غير صحيح.',
'remaining_attempts' => 2
]
[
'success' => false,
'message' => 'انتهت صلاحية رمز التحقق أو لم يتم العثور عليه.'
]
Testing
The package includes comprehensive tests. Run them using:
./vendor/bin/phpunit
./vendor/bin/phpunit --testsuite=Unit
./vendor/bin/phpunit --testsuite=Feature
./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:
| 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, and type fields
- Response Type Enum: Added
OtpResponseType enum 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