asgarihope / pretty-otp
Requires
- php: >=7.2
- illuminate/events: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0
- illuminate/support: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0
- laravel/framework: >=6
Requires (Dev)
- phpunit/phpunit: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0
README
PrettyOtp is a Laravel package designed to simplify the implementation of OTP (One-Time Password) mechanisms for authentication and other secure actions.
Requirements
- PHP >= 8.2
- Laravel 6.x – 13.x
The package is tested against Laravel 6 through 13. Laravel 11+ requires PHP 8.2+, which is the minimum PHP version for this package.
Installation
Install the package via Composer:
composer require asgarihope/pretty-otp
The service provider (PrettyOtp\Laravel\Providers\PrettyOtpServiceProvider) is auto-discovered by Laravel. If you have disabled package discovery, register it manually in config/app.php (Laravel 10 and below) under providers.
Configuration
Publish the package configuration file:
php artisan vendor:publish --tag=otp-config
This will create a configuration file at config/otp.php:
return [ 'otp_expiry' => 5, // OTP validity time in minutes 'otp_attempts' => 5, // Maximum allowed validation attempts per OTP 'otp_length' => 6, // Number of digits in the generated OTP 'otp_retry_time' => 2, // Cooldown (in minutes) before a new OTP can be requested ];
| Key | Description |
|---|---|
otp_expiry |
How long (in minutes) a generated OTP stays valid. |
otp_attempts |
Maximum number of failed validation attempts before the user is locked out and must request a new OTP. |
otp_length |
The number of digits used when generating the OTP. |
otp_retry_time |
Minimum wait time (in minutes) between two OTP requests for the same segment/mobile. |
Middleware
The package provides an otp middleware alias backed by OtpMiddleware to secure routes.
Example Usage
In your routes/web.php or routes/api.php, apply the middleware:
use Illuminate\Support\Facades\Route; Route::middleware(['otp:mobile,login'])->group(function () { Route::post('/secure-action', [SecureController::class, 'handle']); });
Middleware Parameters
The middleware accepts up to three comma-separated parameters: otp:key,segment,lifetimeInHours
key(required): The request input name that holds the identifier (e.g.mobile).segment(required): A string identifying the OTP usage context (e.g.login,reset-password).lifetimeInHours(optional): How many hours access is granted after a successful validation. When omitted (ornull), access is granted indefinitely until explicitly revoked.
// Access granted for 3 hours after a valid OTP Route::middleware(['otp:mobile,login,3'])->post('/secure-action', ...);
Response Metadata
When the middleware short-circuits (OTP required, invalid, locked, etc.), it returns a JSON response that includes two helpful fields alongside the message/error:
{
"message": "OTP sent to 0912xxxxxxx. Please verify.",
"time_remain": 120,
"remain_attempt": 5
}
time_remain: seconds left before a new OTP can be requested.remain_attempt: remaining validation attempts for the current OTP.
Events and Listeners
The package dispatches an OtpRequested event whenever an OTP is requested. Listen to this event to define your custom delivery logic (SMS, email, etc.).
OtpRequested Event
Event Properties
$mobile: The identifier (e.g. mobile number) for which the OTP is requested.$key: The request parameter key used for the identifier.$segment: The OTP usage context.
Implementing a Listener
The package ships an abstract SendOtpListener that already generates the OTP (via OtpService) and builds the message. You only need to implement the sendNotification method:
namespace App\Listeners; use PrettyOtp\Laravel\Listeners\SendOtpListener; class MyOtpListener extends SendOtpListener { public function sendNotification(string $key, string $inputValue, string $message): void { // Send $message to $inputValue via your SMS/email provider. SmsGateway::send($inputValue, $message); } }
Note:
SendOtpListener::handle()callsOtpService::generateOtp(), which both stores the OTP in cache and records its sent-at timestamp. You should not generate or store the OTP yourself.
If you want to use your own listener instead, register it for PrettyOtp\Laravel\Events\OtpRequested in your EventServiceProvider and call $otpService->generateOtp($event->mobile, $event->segment) inside it.
Service Provider
The PrettyOtpServiceProvider automatically:
- Registers the
otpmiddleware alias. - Registers the
OtpRequested→SendOtpListenerbinding. - Merges the default
otpconfig. - Publishes the configuration and translation files.
Translation
Package translations are registered under the pretty-otp namespace and work out of the box without publishing. The middleware uses keys like trans('pretty-otp::otp.invalid_otp').
To customize the messages, publish the translations:
php artisan vendor:publish --tag=otp-translations
Published files land in lang/vendor/pretty-otp/<locale>/otp.php (or resources/lang/vendor/pretty-otp/... on older Laravel versions) and override the package defaults.
Available locales: en, fa.
Example Flow
- Request OTP — A request to a protected route without a valid
otpinput triggers theOtpRequestedevent. The configured listener generates and delivers the OTP. - Validate OTP — The client sends the same request again, now including the
otpinput. The middleware validates it and grants access on success. - Access Grant — On success, access is granted for the configured
lifetimeInHours(or indefinitely when omitted). Subsequent requests within the lifetime skip OTP validation.
Cache Storage
The package uses Laravel's cache to store OTPs, attempt counters, sent-at timestamps, and access grants. Configure your preferred cache driver in config/cache.php.
Contribution
Contributions are welcome! Feel free to submit a pull request or report issues in the repository.
License
This package is open-sourced software licensed under the MIT license.