1. Go to this page and download the library: Download biponix/laravel-secure-otp library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
use Biponix\SecureOtp\Services\SecureOtpService;
$otp = app(SecureOtpService::class);
// Send OTP to any identifier (no validation)
$otp->send('01700000000'); // Bangladesh phone
$otp->send('[email protected]'); // Email
$otp->send('username123'); // Username
$otp->send('12345'); // User ID
// Verify OTP
$verified = $otp->verify('01700000000', '123456');
use Biponix\SecureOtp\Services\SecureOtpService;
use Biponix\SecureOtp\Types\EmailType;
// Register identifier types in AppServiceProvider::boot()
SecureOtpService::addType('email', new EmailType());
// Now use with type parameter
$otp->send('[email protected]', 'email'); // ✅ Validated & normalized
$otp->verify('[email protected]', '123456', 'email');
use Biponix\SecureOtp\Exceptions\InvalidIdentifierException;
use Biponix\SecureOtp\Exceptions\RateLimitExceededException;
use Biponix\SecureOtp\Exceptions\OtpGenerationException;
use Biponix\SecureOtp\Services\SecureOtpService;
class AuthController extends Controller
{
public function sendOtp(Request $request, SecureOtpService $otp)
{
try {
// Send OTP (throws on error)
$otp->send($request->email, 'email');
return response()->json([
'message' => 'OTP sent successfully'
]);
} catch (RateLimitExceededException $e) {
// Rate limit exceeded
return response()->json([
'error' => 'Too many requests',
'retry_after' => $e->getRetryAfter(),
], 429);
} catch (InvalidIdentifierException $e) {
return response()->json(['error' => 'Invalid email address'], 400);
} catch (OtpGenerationException $e) {
return response()->json(['error' => 'Failed to send OTP'], 500);
}
}
public function verifyOtp(Request $request, SecureOtpService $otp)
{
// verify() returns bool (doesn't expose why it failed for security)
$verified = $otp->verify($request->email, $request->code, 'email');
if ($verified) {
// OTP verified successfully
$user = User::where('email', $request->email)->firstOrFail();
auth()->login($user);
return response()->json(['message' => 'Login successful']);
}
return response()->json(['error' => 'Invalid or expired code'], 422);
}
}
use Biponix\SecureOtp\Services\SecureOtpService;
public function __construct(
private SecureOtpService $otp
) {}
public function sendCode(string $identifier): void
{
// Throws exceptions on error
$this->otp->send($identifier);
}
// app/Otp/BangladeshSmsType.php
namespace App\Otp;
use Biponix\SecureOtp\Contracts\OtpIdentifierType;
class BangladeshSmsType extends OtpIdentifierType
{
/**
* Normalize Bangladesh phone numbers to E.164 format
*/
public function normalize(string $value): string
{
// Remove spaces, dashes, parentheses
$value = preg_replace('/[\s\-\(\)]/', '', $value);
// Convert local format (01700000000) to E.164 (+8801700000000)
if (preg_match('/^0\d{10}$/', $value)) {
return '+880' . substr($value, 1);
}
return $value;
}
/**
* Validate E.164 Bangladesh phone numbers
*/
public function validate(string $value): bool
{
// Must be +880 followed by 10 digits
return preg_match('/^\+880\d{10}$/', $value) === 1;
}
}
// app/Providers/AppServiceProvider.php
use App\Otp\BangladeshSmsType;
use Biponix\SecureOtp\Services\SecureOtpService;
public function boot(): void
{
// Register custom identifier types
SecureOtpService::addType('sms', new BangladeshSmsType());
}
// Send OTP with validation
$otp->send('01700000000', 'sms'); // ✅ Normalized to +8801700000000
$otp->send('0170-000-0000', 'sms'); // ✅ Normalized to +8801700000000
// Verify with same type
$verified = $otp->verify('01700000000', '123456', 'sms'); // ✅ Works!
// Username type
class UsernameType extends OtpIdentifierType
{
public function normalize(string $value): string
{
return strtolower(trim($value));
}
public function validate(string $value): bool
{
return preg_match('/^[a-z0-9_]{3,20}$/', $value) === 1;
}
}
// User ID type
class UserIdType extends OtpIdentifierType
{
public function normalize(string $value): string
{
return trim($value);
}
public function validate(string $value): bool
{
return ctype_digit($value) && (int)$value > 0;
}
}
// Register in AppServiceProvider
SecureOtpService::addType('username', new UsernameType());
SecureOtpService::addType('user_id', new UserIdType());
// Usage
$otp->send('john_doe', 'username');
$otp->send('12345', 'user_id');
use Biponix\SecureOtp\Services\SecureOtpService;
use Biponix\SecureOtp\Exceptions\RateLimitExceededException;
public function customDelivery(SecureOtpService $otp)
{
try {
// Generate OTP without sending (returns string)
$code = $otp->generate('[email protected]', 'email');
// Deliver via your custom method
$this->sendViaSms($code);
} catch (RateLimitExceededException $e) {
// Handle rate limiting
return response()->json([
'error' => 'Too many requests',
'retry_after' => $e->getRetryAfter(),
], 429);
}
}
// Default: queued (non-blocking)
$otp->send('[email protected]');
// Force synchronous sending (blocks until sent)
$otp->sendNow('[email protected]');
use Biponix\SecureOtp\Facades\SecureOtp;
// Send OTP (throws exceptions on error)
SecureOtp::send('[email protected]');
// Verify OTP (returns bool)
$verified = SecureOtp::verify('[email protected]', '123456');
// Generate without sending (returns string, throws on rate limit)
$code = SecureOtp::generate('[email protected]');
// Send synchronously (throws exceptions on error)
SecureOtp::sendNow('[email protected]');
// app/Notifications/MultiChannelOtpNotification.php
namespace App\Notifications;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\VonageSmsMessage;
use Illuminate\Notifications\Notification;
class MultiChannelOtpNotification extends Notification
{
public function __construct(public string $code) {}
/**
* Route notification channels based on identifier type
*/
public function via(object $notifiable): array
{
// $notifiable->type comes from SecureOtpService::send($identifier, $type)
return match ($notifiable->type) {
'sms' => ['vonage'], // Phone via SMS
'email' => ['mail'], // Email
'whatsapp' => ['whatsapp'], // WhatsApp (if configured)
default => ['mail'], // Fallback to email
};
}
/**
* SMS notification
*/
public function toVonage(object $notifiable): VonageSmsMessage
{
return (new VonageSmsMessage)
->content("Your verification code is: {$this->code}");
}
/**
* Email notification
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Your Verification Code')
->line("Your verification code is: {$this->code}")
->line('This code will expire in ' . config('secure-otp.expiry_minutes', 5) . ' minutes.');
}
}
// Sends via Vonage SMS
$otp->send('01700000000', 'sms');
// Sends via Email
$otp->send('[email protected]', 'email');
// Sends via WhatsApp (if configured)
$otp->send('+8801700000000', 'whatsapp');
use Illuminate\Support\Facades\Schedule;
Schedule::command('secure-otp:clean --force')
->daily()
->withoutOverlapping()
->onOneServer();
bash
# In development (prompts for confirmation)
php artisan secure-otp:clean
# In production (bypasses confirmation)
php artisan secure-otp:clean --force
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.