Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel One Time Passwords Laravel Package

spatie/laravel-one-time-passwords

Securely generate and consume one-time passwords in Laravel. Ships with notifications (email by default, extensible to SMS/other channels) and a ready-to-use Livewire login component. Optionally enhances the OTP input UI automatically when Flux is installed.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require spatie/laravel-one-time-passwords
    

    Publish the config file:

    php artisan vendor:publish --provider="Spatie\OneTimePassword\OneTimePasswordServiceProvider"
    
  2. Configuration:

    • Review .env for OTP settings (e.g., OTP_EXPIRATION_MINUTES=5).
    • Configure the config/otp.php file to define:
      • model (default: Spatie\OneTimePassword\Models\OneTimePassword).
      • table_name (default: one_time_passwords).
      • expiration (default: 5 minutes).
      • max_attempts (default: 5).
      • New in 1.1.0: enforce_same_origin (default: false) to control cross-origin OTP handling.
  3. First Use Case: Generate and send an OTP to a user via email/SMS:

    use Spatie\OneTimePassword\OneTimePassword;
    
    $otp = OneTimePassword::createForUser($user);
    Mail::to($user->email)->send(new SendOtpEmail($otp));
    

Where to Look First

  • Documentation: Spatie’s OTP Docs (updated for 1.1.0).
  • Config File: config/otp.php for tweaking behavior, including the new enforce_same_origin setting.
  • Model: app/Models/OneTimePassword.php (if extending the default model).
  • Events: Spatie\OneTimePassword\Events\OneTimePasswordGenerated for custom logic on OTP creation.
  • Release Notes: 1.1.0 Changelog for breaking changes or fixes.

Implementation Patterns

Core Workflows

1. Generating and Sending OTPs

  • Default Generation:
    $otp = OneTimePassword::createForUser($user); // Generates a 6-digit code
    
  • Custom Code Length:
    $otp = OneTimePassword::createForUser($user, codeLength: 8);
    
  • Sending via Email/SMS: Use Laravel’s Mail or Notifiable facade to dispatch the OTP:
    $user->notify(new SendOtpNotification($otp->code));
    

2. Verifying OTPs

  • Basic Verification:
    if (OneTimePassword::verify($user, $code)) {
        // OTP is valid; proceed with authentication.
    }
    
  • With Custom Logic:
    $otp = OneTimePassword::findValidForUser($user);
    if ($otp && $otp->code === $code) {
        // Custom validation (e.g., check IP, device fingerprint).
    }
    

3. Integration with Authentication

  • OTP Middleware: Create middleware to enforce OTP verification before login:

    namespace App\Http\Middleware;
    
    use Closure;
    use Spatie\OneTimePassword\OneTimePassword;
    
    class VerifyOtp
    {
        public function handle($request, Closure $next)
        {
            if (!$request->user() || !OneTimePassword::verify($request->user(), $request->input('otp'))) {
                return redirect()->route('login.otp');
            }
            return $next($request);
        }
    }
    

    Register in app/Http/Kernel.php:

    protected $routeMiddleware = [
        'otp.verify' => \App\Http\Middleware\VerifyOtp::class,
    ];
    
  • OTP Login Route:

    Route::post('/login/otp', function (Request $request) {
        $request->validate(['otp' => 'required|string']);
        if (OneTimePassword::verify($request->user(), $request->otp)) {
            Auth::login($request->user());
            return redirect()->intended('/dashboard');
        }
        return back()->withErrors(['otp' => 'Invalid code.']);
    });
    

4. Rate Limiting and Security

  • Max Attempts: The package enforces max_attempts (default: 5) per OTP. Customize in config/otp.php:
    'max_attempts' => 3, // Reduce for stricter security.
    
  • Lockout Logic: Extend the OneTimePassword model to track failed attempts:
    public function markAttempt($user)
    {
        $this->failed_attempts++;
        $this->save();
        if ($this->failed_attempts >= config('otp.max_attempts')) {
            $this->markAsUsed(); // Lock the OTP.
        }
    }
    
  • Same Origin Enforcement (New in 1.1.0): Enable enforce_same_origin in config/otp.php to restrict OTP verification to the same origin:
    'enforce_same_origin' => env('OTP_ENFORCE_SAME_ORIGIN', true),
    
    This prevents cross-origin OTP verification attempts, improving security for SPAs or multi-origin apps.

5. Testing OTPs

  • Unit Tests: Use OneTimePassword::fake() to mock OTPs in tests:
    use Spatie\OneTimePassword\Testing\FakesOneTimePassword;
    
    public function test_otp_verification()
    {
        $this->actingAs($user);
        OneTimePassword::fake(['123456']); // Fake a valid OTP.
    
        $response = $this->post('/login/otp', ['otp' => '123456']);
        $response->assertRedirect('/dashboard');
    }
    

Integration Tips

1. Custom Storage

Override the default storage (e.g., Redis) by binding a custom repository:

// app/Providers/AppServiceProvider.php
public function register()
{
    $this->app->bind(
        \Spatie\OneTimePassword\Repositories\OneTimePasswordRepository::class,
        \App\Repositories\CustomOtpRepository::class
    );
}

2. Multi-Factor Authentication (MFA)

Combine with Laravel’s built-in auth:

// After email/password login, require OTP.
if (Auth::check() && !session()->has('otp_verified')) {
    return redirect()->route('verify.otp');
}

3. Internationalization

Localize OTP messages by extending the notification:

// app/Notifications/SendOtpNotification.php
public function toMail($notifiable)
{
    return (new MailMessage)
        ->subject(__('OTP Verification'))
        ->line(__('Your OTP code is :code', ['code' => $this->code]));
}

4. Logging

Log OTP events (e.g., generation, verification) using Laravel’s logging:

// In a service or controller.
\Log::info('OTP generated for user', ['user_id' => $user->id, 'code' => $otp->code]);

5. Cross-Origin Handling (New in 1.1.0)

If using enforce_same_origin = true, ensure your frontend sends OTP verification requests from the same origin. For SPAs, this may require:

  • Configuring CORS headers to allow same-origin requests.
  • Using a backend proxy for cross-origin OTP verification.

Gotchas and Tips

Pitfalls

1. Expiration Handling

  • Issue: OTPs expire silently if not verified in time.
  • Fix: Add a user-friendly message:
    if (!OneTimePassword::verify($user, $code)) {
        $otp = OneTimePassword::findValidForUser($user);
        if (!$otp) {
            return back()->withErrors(['otp' => __('OTP has expired.')]);
        }
        return back()->withErrors(['otp' => __('Invalid code.')]);
    }
    

2. Race Conditions

  • Issue: Concurrent requests to verify the same OTP may cause inconsistencies.
  • Fix: Use database transactions or optimistic locking:
    DB::transaction(function () use ($user, $code) {
        $otp = OneTimePassword::findValidForUser($user);
        if ($otp && $otp->code === $code) {
            $otp->markAsUsed();
            // Proceed with auth.
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport