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 Advanced Otp Laravel Package

mkd/laravel-advanced-otp

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require mkd/laravel-advanced-otp
    php artisan vendor:publish --provider="LaravelAdvancedOTP\ServiceProvider" --tag="config"
    
    • Publish the config file to customize OTP settings (e.g., otp_length, expire_after_minutes).
  2. Generate a Custom OTP Method:

    php artisan magic-otp:make LoginOTP
    
    • This creates a new OTP method class (e.g., app/OtpMethods/LoginOTP.php) with default logic for generation, storage, and validation.
  3. First Use Case:

    • Trigger OTP Generation:
      use LaravelAdvancedOTP\Facades\LaravelAdvancedOTP;
      
      $otp = LaravelAdvancedOTP::handle(LoginOTP::class, [
          'secret' => 'your_app_secret', // Required for hashing
          'email' => 'user@example.com',
      ]);
      
    • Verify OTP:
      $isValid = LaravelAdvancedOTP::verify(LoginOTP::class, [
          'secret' => 'your_app_secret',
          'email' => 'user@example.com',
          'otp' => $userInputOTP,
      ]);
      

Where to Look First

  • Config File: config/laravel-advanced-otp.php – Adjust OTP length, expiration, and storage drivers (default: cache).
  • OTP Methods: app/OtpMethods/ – Extend or override default logic for custom workflows (e.g., SMS, database-backed OTPs).
  • Facade: LaravelAdvancedOTP – Centralized API for generation, verification, and cleanup.

Implementation Patterns

Core Workflows

  1. Email-Based OTP Flow:

    • Generate & Send:
      $otp = LaravelAdvancedOTP::handle(LoginOTP::class, [
          'secret' => 'app_secret',
          'email' => $user->email,
          'template' => 'emails.otp', // Custom Blade template
      ]);
      
      • Uses the LoginOTP method’s generate() and send() logic (default: stores hashed OTP in cache).
    • Verify:
      if (LaravelAdvancedOTP::verify(LoginOTP::class, [
          'secret' => 'app_secret',
          'email' => $user->email,
          'otp' => request('otp'),
      ])) {
          // Authenticate user...
      }
      
  2. Custom Storage/Validation:

    • Override generate() in LoginOTP to use a database:
      public function generate(array $data): string
      {
          $otp = Str::random($this->config['otp_length']);
          $hashed = hash_hmac('sha256', $otp, $data['secret']);
          UserOtp::create([
              'user_email' => $data['email'],
              'otp' => $hashed,
              'expires_at' => now()->addMinutes($this->config['expire_after_minutes']),
          ]);
          return $otp; // Return plain OTP for sending
      }
      
    • Override validate() to check against your storage:
      public function validate(array $data): bool
      {
          $hashedInput = hash_hmac('sha256', $data['otp'], $data['secret']);
          return UserOtp::where('user_email', $data['email'])
              ->where('otp', $hashedInput)
              ->where('expires_at', '>', now())
              ->exists();
      }
      
  3. Rate Limiting:

    • Use Laravel’s throttle middleware or extend LoginOTP to track failed attempts:
      public function validate(array $data): bool
      {
          $attempts = cache()->get("otp_attempts_{$data['email']}", 0);
          if ($attempts >= 5) {
              throw new \Exception('Too many attempts. Try again later.');
          }
          // ... rest of validation
      }
      
  4. Integration with Auth:

    • Create a custom guard or use middleware:
      public function handle(Request $request, Closure $next)
      {
          if (!$request->has('otp')) {
              return redirect()->route('generate.otp');
          }
          if (!LaravelAdvancedOTP::verify(LoginOTP::class, [
              'secret' => config('app.otp_secret'),
              'email' => auth()->user()->email,
              'otp' => $request->otp,
          ])) {
              return back()->withErrors(['otp' => 'Invalid OTP']);
          }
          return $next($request);
      }
      

Pro Tips

  • Reuse OTP Methods: Create a base OtpMethod class (e.g., DatabaseOtpMethod) and extend it for consistency across projects.
  • Logging: Extend validate() to log failed attempts:
    \Log::warning("OTP failure for {$data['email']}", ['otp' => $data['otp']]);
    
  • Testing: Use LaravelAdvancedOTP::fake() in tests to mock OTPs:
    LaravelAdvancedOTP::fake(LoginOTP::class, '123456');
    $this->assertTrue(LaravelAdvancedOTP::verify(...));
    

Gotchas and Tips

Pitfalls

  1. Secret Key Management:

    • The secret parameter is required for hashing and must be consistent between generation and verification.
    • Gotcha: Hardcoding secrets in OTP methods breaks security. Use Laravel’s config() or environment variables:
      'secret' => config('app.otp_secret'),
      
    • Fix: Publish the config and set OTP_SECRET in .env.
  2. Cache Expiration:

    • Default storage (cache) relies on Laravel’s cache driver. If using file or database, ensure TTL is set:
      cache()->put("otp_{$email}", $hashedOtp, now()->addMinutes($this->config['expire_after_minutes']));
      
    • Gotcha: Forgetting to clean up expired OTPs can lead to stale validations.
    • Fix: Call LaravelAdvancedOTP::cleanup(LoginOTP::class) periodically (e.g., via a scheduled job).
  3. OTP Length vs. Security:

    • Short OTPs (e.g., 4 digits) are easier for users but less secure. Default is 6 digits.
    • Tip: Use 8+ digits for sensitive actions (e.g., password resets).
  4. Race Conditions:

    • If OTPs are generated/verified in parallel (e.g., API calls), use atomic operations:
      public function validate(array $data): bool
      {
          return cache()->get("otp_{$data['email']}", function () use ($data) {
              return false; // Expired or invalid
          }) === hash_hmac('sha256', $data['otp'], $data['secret']);
      }
      
  5. Custom Validation Logic:

    • Overriding validate() requires careful handling of edge cases (e.g., empty OTP, expired tokens).
    • Tip: Use Laravel’s Validator for reusable rules:
      use Illuminate\Support\Facades\Validator;
      
      public function validate(array $data): bool
      {
          $validator = Validator::make($data, [
              'otp' => ['required', 'string', function ($attribute, $value, $fail) {
                  if (!$this->isValidOtp($value, $data)) {
                      $fail('The OTP is invalid.');
                  }
              }],
          ]);
          return !$validator->fails();
      }
      

Debugging

  1. Verification Fails Silently:

    • Enable debug mode in the config:
      'debug' => env('APP_DEBUG', false),
      
    • Check logs for hashed OTP values (useful for debugging mismatches).
  2. OTP Not Found in Cache:

    • Verify the cache key format (default: otp_{$email}). Override in your OTP method:
      protected function getCacheKey(array $data): string
      {
          return "custom_otp_{$data['email']}_{$data['secret']}";
      }
      
  3. Performance Issues:

    • Database-backed OTPs can slow down validation. Optimize with indexes:
      // Migration for UserOtp table
      $table->string('user_email');
      $table->string('otp');
      $table->timestamp('expires_at');
      $table->index(['user_email', 'expires_at']);
      

Extension Points

  1. Custom Storage Drivers:
    • Implement LaravelAdvancedOTP\Contracts\OtpStorage
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.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope