Weave 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 Otp Manager Laravel Package

salehhashemi/laravel-otp-manager

Laravel OTP Manager provides a simple, secure way to generate, send, and verify one-time passwords in Laravel applications. It supports multiple delivery channels, configurable expiry, rate limiting, and middleware to protect routes and actions with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and let Laravel discover it:

composer require salehhashemi/laravel-otp-manager
php artisan vendor:publish --tag=otp-manager-config
php artisan migrate

The published config/otp-manager.php is the first file to read. It controls the code length, expiry, resend interval, max send attempts, and the notification class used to deliver the code. If those defaults don't match your product (most apps want 4–6 digits, 2–5 minute expiry, a 60-second resend cooldown), change them before writing any application code.

The package ships a facade as the primary entry point:

use Salehhashemi\OtpManager\Facades\OtpManager;

$sent   = OtpManager::send($mobile);
$valid  = OtpManager::verify($mobile, $request->code);

The first use case to wire up is passwordless login or phone verification: send an OTP to a phone number/email, verify it, then issue a session or Sanctum token. Do that end-to-end in a throwaway route before layering it into real auth flows, because it exercises the config, notification, and database table all at once.

Look at the package's Notifications directory next — that is where channel and template customization lives, and it's the extension point you will touch most often.

Implementation Patterns

Wrap the facade in your own service. Don't call OtpManager directly from controllers. Create an OtpService or action classes (SendOtp, VerifyOtp) so you can normalize identifiers, add context (login vs. phone verification vs. password reset), and test without the facade:

final class SendLoginOtp
{
    public function __invoke(string $mobile): void
    {
        $mobile = PhoneNumber::normalize($mobile); // always normalize first
        OtpManager::send($mobile);
    }
}

Pair verification with token issuance. The verify call returns a boolean, so the controller pattern is: validate the request, call verify, and on true issue $user->createToken(...) or Auth::login($user). Never log the user in before verification succeeds, and clear any pending OTP state on success.

Customize the notification, not the package. The config exposes a notification class. Subclass or replace it to control the SMS/email channel, the message body, and localization. This is the cleanest way to brand the message and to route through your existing SMS driver instead of adding a second provider.

Use it as a model concern where it fits. If the package provides a trait for authenticatable models, use it for $user->sendOtp() style calls; otherwise keep the identifier-based facade API and treat OTPs as a separate concern from the user record. Mixing both approaches in one codebase gets confusing fast.

Handle resend explicitly. Treat "resend" as a distinct endpoint with its own throttle middleware. The package's send-interval config gives you a baseline, but a route-level throttle: rule is what actually stops abuse from a scripted client.

Test with notification fakes and config overrides. In feature tests, Notification::fake() and assert the OTP notification was sent, then read the code from the notification or override the code length/expiry in the test. For unit tests, bind a fake around your own OtpService rather than the vendor facade.

Separate OTP purposes if your product needs it. Login codes and "verify your new phone number" codes should not be interchangeable. If the package keys only on the identifier, namespace it (e.g. prefix the identifier with the purpose) or keep a small application-level table that records the intent.

Gotchas and Tips

  • Pin the version. With zero dependents there is no ecosystem pressure testing upgrades for you. Use a caret constraint and read the changelog on every minor bump; the last release date alone tells you the package is active but not that it's stable.
  • Expiry units are a classic trap. Confirm whether the config value is minutes or seconds, and whether expiry is evaluated against now() in the app timezone. Write one test that travels time past the expiry and asserts failure.
  • Codes are likely stored in plaintext. Check the migration. If so, treat the otps table as sensitive: short expiry, delete-on-use, and no code values in logs or exception traces. If you need hashed storage, that's a fork or a custom model, not a config toggle.
  • Delete the OTP on successful verification. Otherwise a code remains valid until expiry and replay becomes possible. Verify this behaviour explicitly rather than assuming it.
  • Normalize identifiers before they hit the package. +1 (555) 010-9999 and 15550109999 must resolve to the same database row, or users get multiple live codes and confusing failures. Normalize in one place, at the edge.
  • Queued notifications need a worker. If the notification implements ShouldQueue, a missing queue worker means "OTP sent" responses with no code arriving. Either run the worker in every environment or force the notification to send synchronously.
  • Silent channel failures are common. A misconfigured SMS driver often fails quietly. Log delivery results and monitor the send path; don't rely solely on the boolean return from send().
  • Brute-force protection is your job. A 6-digit code has a million combinations; the package's attempt limits help, but add per-IP and per-identifier route throttling and lock the identifier after repeated failures.
  • Race conditions on verify. Two concurrent requests can both read a valid code. If the package doesn't do it atomically, wrap verification in a transaction or a cache lock.
  • Extension points to prefer, in order: published config → notification class → your own service wrapper. Avoid editing vendor files or binding over internal classes; you'll lose those changes on the next release.
  • Watch the migration. If the package auto-loads migrations, a future release may add columns your production database won't have. Publish the migration if the package supports it and own the schema.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib