Installation:
composer require mkd/laravel-otp
Publish the config file (if needed):
php artisan vendor:publish --provider="MKD\LaravelOTP\LaravelOTPServiceProvider"
First Use Case: Generate a TOTP secret and verify an OTP in a controller or command:
use MKD\LaravelOTP\LaravelOTP;
$secret = LaravelOTP::generateSecret(); // Auto-generates a Base32 secret
$otpService = LaravelOTP::make($secret);
// Generate OTP (6 digits, default 30s window)
$otp = $otpService->now();
Where to Look First:
config/laravel-otp.php (if published) for customization (e.g., OTP length, time step).LaravelOTPServiceProvider for binding extensions or overrides.LaravelOTP::generateQRCode() for TOTP setup in authenticator apps.Setup:
$secret = LaravelOTP::generateSecret();
$qrCodeUrl = LaravelOTP::generateQRCode('user@example.com', $secret);
$secret in the user model (e.g., user_otp_secret).$qrCodeUrl in a view for authenticator apps (Google Authenticator, Authy).Verification:
$otpService = LaravelOTP::make($user->otp_secret);
$isValid = $otpService->verifyTOTP($userInputOTP, [
'allowedDeviation' => 1, // Allow ±1 time step (e.g., 30s window)
]);
$hotpService = LaravelOTP::make($secret, 'hotp');
$counter = 0; // Track counter (e.g., per login attempt)
$otp = $hotpService->generateHOTP($counter);
$isValid = $hotpService->verifyHOTP($userInputOTP, $counter);
use MKD\LaravelOTP\Middleware\VerifyOTP;
Route::middleware([VerifyOTP::class])->group(function () {
// Routes requiring OTP
});
LaravelOTP logic in a custom guard or use it alongside MustVerifyOTP interface.// User.php
use MKD\LaravelOTP\Traits\HasOTP;
class User extends Authenticatable
{
use HasOTP;
protected $otpSecret;
protected $otpAlgorithm = 'totp'; // or 'hotp'
}
$secret = User::find($id)->otp_secret;
$otpService = LaravelOTP::make($secret);
Combine with Laravel’s rate limiting to prevent brute-force attacks:
Route::middleware(['throttle:5,1'])->group(function () {
Route::post('/verify-otp', [OTPController::class, 'verify']);
});
if (!$otpService->verifyTOTP($otp)) {
$user->sendFallbackVerification();
}
Use the LaravelOTP facade in tests:
public function test_otp_verification()
{
$secret = LaravelOTP::generateSecret();
$otpService = LaravelOTP::make($secret);
$otp = $otpService->now();
$this->assertTrue($otpService->verifyTOTP($otp));
}
Secret Storage:
Time Synchronization (TOTP):
Carbon or DateTime for consistency:
$otpService->setTimeSource(function () {
return Carbon::now()->getTimestamp();
});
allowedDeviation (e.g., 1) to account for minor time differences.HOTP Counters:
QR Code Generation:
issuer parameter in generateQRCode() is URL-encoded:
$qrCodeUrl = LaravelOTP::generateQRCode(
urlencode('MyApp'),
$secret
);
Algorithm Confusion:
OTP Generation:
LaravelOTP::make($secret)->now() to debug expected OTPs.$timeStep = $otpService->getCurrentTimeStep();
Verification Issues:
allowedDeviation if OTPs fail due to time sync (e.g., 3 for ±90s).Logging:
\Log::debug('OTP Verification', [
'input' => $userInputOTP,
'expected' => $otpService->now(),
'valid' => $otpService->verifyTOTP($userInputOTP),
]);
Custom Algorithms:
Extend the MKD\LaravelOTP\Contracts\OTPGenerator interface to support custom algorithms.
Event Listeners:
Listen for OTP events (e.g., otp.verified):
LaravelOTP::addListener('otp.verified', function ($user, $otp) {
// Trigger post-verification logic
});
Configuration Overrides:
Override defaults in config/laravel-otp.php:
'otp' => [
'length' => 8, // Default: 6
'algorithm' => 'sha1', // Default: 'sha256'
'time_step' => 30, // Seconds for TOTP
],
Service Binding: Bind custom implementations in the service provider:
$this->app->bind('otp', function () {
return new CustomOTPService();
});
How can I help you explore Laravel packages today?