spatie/laravel-one-time-passwords
Generate and verify secure one-time passwords (6‑digit by default) in Laravel. Sends OTPs via mail notifications (extendable to SMS/other channels) and includes a Livewire login component. Optional Flux support provides an enhanced OTP input UI.
Installation:
composer require spatie/laravel-one-time-passwords
php artisan migrate # Creates `one_time_passwords` table
Publish Config (Optional):
php artisan vendor:publish --tag="one-time-passwords-config"
config/one-time-passwords.php for default OTP length, expiration, etc.First Use Case: Trigger OTP for a user and handle login:
// In a controller or service
$user = User::find(1);
$user->sendOneTimePassword(); // Sends OTP via email (default)
// Later, validate OTP
$result = $user->attemptLoginUsingOneTimePassword($request->input('otp'));
if ($result->isOk()) {
return redirect()->intended('dashboard');
}
config/one-time-passwords.php (expiration, security rules)app/Notifications/OneTimePasswordNotification.php (customize email/SMS)vendor/spatie/laravel-one-time-passwords/src/Livewire/OneTimePassword.php (for UI integration)OTP Generation & Delivery:
// Send OTP via email (default)
$user->sendOneTimePassword();
// Custom notification (e.g., SMS)
$user->notify(new CustomOneTimePasswordNotification());
OTP Validation:
// Validate and log in (handles session regeneration)
$result = $user->attemptLoginUsingOneTimePassword($otp);
// Validate without login (e.g., for passwordless flows)
$result = $user->consumeOneTimePassword($otp);
Livewire Integration:
<!-- Basic usage (email + OTP form) -->
<livewire:one-time-password />
<!-- Pre-filled email (skip email step) -->
<livewire:one-time-password email="user@example.com" />
<!-- Custom redirect -->
<livewire:one-time-password :redirect-to="route('admin.dashboard')" />
Passwordless Auth:
Combine with Laravel’s auth:attempt or auth:login after OTP validation:
if ($result->isOk()) {
auth()->login($user, $request->has('remember'));
}
Rate Limiting:
Use Laravel’s throttle middleware or extend the RateLimitExceeded case in ConsumeOneTimePasswordResult.
Multi-Channel OTPs:
Extend OneTimePasswordNotification to support SMS/Slack:
class CustomNotification extends OneTimePasswordNotification {
public function via($notifiable) {
return ['mail', 'vonage']; // SMS via Vonage
}
}
Testing:
Use OneTimePassword::factory() in PHPUnit:
$otp = OneTimePassword::factory()->create(['password' => '123456']);
$result = $user->attemptLoginUsingOneTimePassword('123456');
Origin Enforcement:
EnforceOrigin action class:
'enforce_origin' => false, // config/one-time-passwords.php
Session Hijacking:
$request->session()->regenerate() after OTP login to prevent fixation.Notification Delays:
createOneTimePassword() + manual delivery (e.g., SMS API call) for synchronous flows.Livewire Caching:
Livewire::test('one-time-password')
->set('email', 'test@example.com')
->call('sendOneTimePassword');
Check OTP Table:
SELECT * FROM one_time_passwords WHERE user_id = 1;
password, expires_at, and ip_address/user_agent match.Log Validation Errors:
$result->validationMessage(); // Returns user-friendly error
Common messages:
"DifferentOrigin" → Check IP/User-Agent."OneTimePasswordExpired" → Adjust expiration_minutes in config.Custom Actions:
Override actions in app/Providers/AppServiceProvider:
use Spatie\OneTimePasswords\Actions\CreateOneTimePassword;
use Spatie\OneTimePasswords\Actions\ConsumeOneTimePassword;
public function register()
{
$this->app->bind(
CreateOneTimePassword::class,
CustomCreateOneTimePassword::class
);
}
Custom OTP Format:
Modify the password field in the OneTimePassword model or use a custom action:
// Example: Alphanumeric OTP
$otp = Str::random(8); // Replace default numeric OTP
Webhook Validation: Validate OTPs via API (e.g., for server-to-server auth):
$otp = OneTimePassword::where('password', $request->otp)
->where('user_id', $user->id)
->first();
if ($otp && !$otp->isExpired()) {
$otp->delete(); // Consume
return response()->json(['success' => true]);
}
Flux Integration: Ensure Flux is installed after the package:
composer require fluxui/flux
The package auto-detects Flux and uses its OTP input component.
Database Events: Listen for OTP creation/consumption:
OneTimePassword::created(function ($otp) {
Log::info("OTP sent to {$otp->user->email}");
});
How can I help you explore Laravel packages today?