ichtrojan/laravel-otp
A lightweight Laravel package for generating, storing, and verifying one-time passwords (OTPs). It provides configurable code length and expiry, simple validation helpers, and database-backed storage, making it easy to add OTP-based verification to Laravel applications.
composer require the package at ^2.0, then publish its config and migrations (php artisan vendor:publish), and run php artisan migrate — OTP packages typically need a table to track issued tokens and expiry.config/otp.php (or similarly named). In v2.0.0 this is where you choose numeric vs. alphanumeric tokens; confirm the default before writing any tests, since the shape of a generated token changed for anyone opting into alphanumeric.Otp::generate() snippets from v1 tutorials.use App\Services\OtpGate; // your thin wrapper, or inject the package service directly
public function send(OtpGate $otp)
{
$token = $otp->generate($user->phone); // numeric by default, alphanumeric if configured
// send via notification / SMS driver
}
php artisan otp:clean once manually before wiring it into the scheduler — v2.0.0 improved its output, so you can see exactly what it pruned and confirm your expiry config is sane.Constructor injection over facades. Since facades are gone, inject the service into controllers, form requests, jobs, and notification classes. This is the single biggest day-to-day change from v1: every call site needs a dependency, not a static alias resolved from config/app.php.
Validate inside a Form Request. The refactored validate() is the natural home for verification logic. Call it from rules() or withValidator() so failed OTPs surface as normal validation errors rather than exceptions bubbling out of a controller.
public function rules(OtpService $otp): array
{
return [
'code' => [
'required',
function ($attribute, $value, $fail) use ($otp) {
if (! $otp->validate($this->input('phone'), $value)) {
$fail('That code is invalid or has expired.');
}
},
],
];
}
Wrap it in your own abstraction. Because the public API just changed across a major version (validate() refactor + facade removal), put a small OtpGate interface in app/ and bind the package to it. Future majors then touch one file instead of every controller.
Alphanumeric tokens need a normalisation step. If you enable alphanumeric generation, decide on case handling at the boundary — uppercase on generate, uppercase the user input before validating — otherwise support tickets about "the code doesn't work" will be caused by autocapitalise on mobile keyboards.
Schedule cleanup. Register otp:clean in routes/console.php or the scheduler (e.g. hourly). With v2.0.0's improved reporting, pipe the output to your log channel in staging for a week to confirm the retention window matches your token TTL.
Delivery stays your responsibility. The package generates and validates; sending is yours. Fire the token through a queued notification so a slow SMS gateway doesn't hold the HTTP request, and never persist the plaintext token in your own tables.
Otp:: call, any alias in config/app.php, and any test using Otp::shouldReceive(...) will fail. Grep your codebase for the facade alias before upgrading; static analysis will not catch string-based container lookups.validate() changed shape. Don't assume the v1 return contract. Check whether the refactored method returns a bool, throws, or consumes the token as a side effect — and write one test asserting a second call with the same code behaves the way you expect (single-use vs. reusable).php artisan config:clear after editing token length/expiry, or your otp:clean retention window and generation format will silently stay on old values in local dev.otp:clean is destructive. Confirm the retention window covers your longest TTL plus any grace period, and avoid running it against production without first running it in a lower environment to read the new v2.0.0 output.OtpGate interface in the container during tests rather than hitting the real service; facades are gone, so Facade::fake() patterns from v1 examples are dead ends.otp:clean still reconciles whatever store you choose.How can I help you explore Laravel packages today?