├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config └── otp.php ├── database └── migrations │ ├── 2023_05_28_144254_create_otps_table.php │ └── 2024_09_27_025709_add_event_to_otps_table.php └── src ├── Exceptions ├── OtpExpiredException.php └── OtpInvalidException.php ├── LaravelOtpServiceProvider.php ├── Models └── Otp.php ├── Otp.php └── Traits └── Otpable.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | .phpunit.result.cache 4 | /.fleet 5 | /.idea 6 | /.vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Prem Chand Saini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel 2 |

LaravelOtp : generate OTP and Validate OTP

3 | 4 | --- 5 | 6 | ![GitHub](https://img.shields.io/github/license/signaturetech/laravel-otp) 7 | ![Packagist Downloads](https://img.shields.io/packagist/dt/signaturetech/laravel-otp) 8 | 9 | ## Table of contents 10 | 11 | - [Introduction](#introduction) 12 | - [Todo](#todo) 13 | - [License](#license) 14 | 15 | ## Introduction 16 | 17 | `LaravelOtp` is a [Laravel](https://laravel.com/) package, designed to generate OTP and Validate OTP using simple steps. This packages will show all OTP history. 18 | 19 | ## Todo 20 | 21 | - [x] Generate OTP 22 | - [x] Verify OTP 23 | - [x] Methods 24 | - [ ] OTP Lists 25 | - [ ] Test Cases 26 | 27 | ## Features 28 | 29 | - Generate OTP 30 | - Verify OTP 31 | - Get the User OTP List 32 | - Generate custom lenth, expiry and formate OTP 33 | 34 | ## Installation & Configuration 35 | 36 | You can install this package via composer using: 37 | 38 | ``` 39 | composer require signaturetech/laravel-otp 40 | ``` 41 | 42 | Next run the command below to setup api-response.config file, you can set your configuration. 43 | 44 | ``` 45 | php artisan vendor:publish --tag=otp-config 46 | ``` 47 | 48 | Now add the `use SignatureTech\LaravelOtp\Traits\Otpable` trait to your model. 49 | 50 | ``` 51 | use SignatureTech\LaravelOtp\Traits\Otpable; 52 | 53 | class User extends Authenticatable 54 | { 55 | use HasApiTokens, HasFactory, Notifiable, Otpable; 56 | } 57 | ``` 58 | 59 | ### Generate OTP 60 | 61 | Please use below code to generate otp: 62 | 63 | 1. Get User Details 64 | 65 | ``` 66 | use App\Models\User; 67 | 68 | $user = User::first(); 69 | ``` 70 | 71 | 2. Create Otp Instance 72 | 73 | ``` 74 | use SignatureTech\LaravelOtp\Otp; 75 | 76 | 77 | $otp = Otp::for($user->email)->generate(); 78 | ``` 79 | 80 | **Note:** You can use email/mobile/phone number to generate otp Just pass the detail using `for` method. 81 | 82 | **Note:** You can use more method to setting otp all methods described in `methods` section. 83 | 84 | 3. Attach Otp with user 85 | 86 | ``` 87 | $userOtp = $user->createOtp($otp); 88 | 89 | $otp = $user->otp; 90 | ``` 91 | 92 | ### Verify OTP 93 | 94 | You can verify otp by using below code: 95 | 96 | 1. Get the use details 97 | 98 | ``` 99 | use App\Models\User; 100 | 101 | $user = User::first(); 102 | ``` 103 | 104 | 2. Get Otp Instance 105 | 106 | ``` 107 | use SignatureTech\LaravelOtp\Otp; 108 | 109 | 110 | $otp = Otp::for($user->email)->getOtp(); 111 | ``` 112 | 113 | 3. Verify Otp 114 | 115 | ``` 116 | try { 117 | $user->verifyOtp($otp, $request->get('otp')); 118 | } catch (OtpInvalidException $e) { 119 | return $e->getMessage; 120 | } catch (OtpExpiredException $e) { 121 | return $e->getMessage; 122 | } 123 | ``` 124 | 125 | ### Create OTP without model 126 | 127 | You can also create otp without model using the following: 128 | 129 | 1. Create OTP 130 | 131 | ``` 132 | use SignatureTech\LaravelOtp\Otp; 133 | 134 | 135 | $otp = Otp::for($user->email)->create(); 136 | ``` 137 | 138 | **Note:** You can use email/mobile/phone number to generate otp Just pass the detail using `for` method. 139 | 140 | **Note:** You can use more method to setting otp all methods described in `methods` section. 141 | 142 | 2. Verify Otp 143 | 144 | ``` 145 | try { 146 | $otp->verifyOtp($request->get('otp')); 147 | } catch (OtpInvalidException $e) { 148 | return $e->getMessage; 149 | } catch (OtpExpiredException $e) { 150 | return $e->getMessage; 151 | } 152 | ``` 153 | 154 | ## Methods 155 | 156 | 1. Set length of otp 157 | 158 | ``` 159 | use SignatureTech\LaravelOtp\Otp; 160 | 161 | // Set Length of OTP 162 | $otp = Otp::for($user->email)->setLength(4)->generate(); 163 | ``` 164 | 165 | **Note:** Default length is 6 digit and you can change the default digit to add the `OTP_LENGTH=4` in `.env` or `config/otp.php` file 166 | 167 | 2. Set Format (Available Format: alpha | alphanumeric | numeric) 168 | 169 | ``` 170 | use SignatureTech\LaravelOtp\Otp; 171 | 172 | // Set Format (Available Format: alpha | alphanumeric | numeric) 173 | $otp = Otp::for($user->email)->setFormat('numeric')->generate(); 174 | ``` 175 | 176 | **Note:** Default format is numeric and you can change the default format to add the `OTP_FORMAT=4` in `.env` or `config/otp.php` file 177 | 178 | 2. Set Expiry (In minutes) 179 | 180 | ``` 181 | use SignatureTech\LaravelOtp\Otp; 182 | 183 | // Set Format (Available Format: alpha | alphanumeric | numeric) 184 | $otp = Otp::for($user->email)->setExpiry(20)->generate(); 185 | ``` 186 | 187 | **Note:** Default expiry is 10 minutes and you can change this to add the `OTP_EXPIRY=20` in `.env` or `config/otp.php` file 188 | 189 | ## License 190 | 191 | - Written and copyrighted ©2022 by Prem Chand Saini ([prem@signaturetech.in](mailto:prem@signaturetech.in)) 192 | - ResponseBuilder is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) 193 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signaturetech/laravel-otp", 3 | "type": "library", 4 | "description": "A package to generate and manage otp", 5 | "keywords": [ 6 | "otp", 7 | "laravel", 8 | "mobile", 9 | "email", 10 | "laravel-otp" 11 | ], 12 | "license": "MIT", 13 | "require": { 14 | "php": ">=8.0", 15 | "laravel/framework": ">= 9.0" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "SignatureTech\\LaravelOtp\\": "src/" 20 | } 21 | }, 22 | "autoload-dev": { 23 | "psr-4": { 24 | "SignatureTech\\LaravelOtp\\Tests\\": "tests/" 25 | } 26 | }, 27 | "authors": [ 28 | { 29 | "name": "Prem Chand Saini", 30 | "email": "prem@signaturetech.in" 31 | } 32 | ], 33 | "minimum-stability": "stable", 34 | "version": "1.0.4", 35 | "extra": { 36 | "laravel": { 37 | "providers": [ 38 | "SignatureTech\\LaravelOtp\\LaravelOtpServiceProvider" 39 | ] 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /config/otp.php: -------------------------------------------------------------------------------- 1 | env('OTP_TABLE', 'otps'), 17 | 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | OTP format 22 | |-------------------------------------------------------------------------- 23 | | This option control the format of the OTP. 24 | | Supported: "alpha", "alphanumeric", "numeric" 25 | | 26 | | alpha : 'a-z', 'A-Z' 27 | | alphanumeric : 'a-z', 'A-Z', '0-9' 28 | | numeric : '0-9' 29 | | 30 | */ 31 | 'format' => env('OTP_FORMAT', 'numeric'), 32 | 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | OTP characters length 37 | |-------------------------------------------------------------------------- 38 | | 39 | | Number of characters of OTP 40 | | 41 | */ 42 | 'length' => env('OTP_LENGTH', 6), 43 | 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | OTP expiration 48 | |-------------------------------------------------------------------------- 49 | | 50 | | Number of minutes before OTP expires 51 | | 52 | */ 53 | 'expiry' => env('OTP_EXPIRY', 10), 54 | 55 | /* 56 | |-------------------------------------------------------------------------- 57 | | Default OTP 58 | |-------------------------------------------------------------------------- 59 | | 60 | | Default OTP for the local model 61 | | 62 | | APP_ENV=local 63 | | 64 | */ 65 | 'default_otp' => env('DEFAULT_OTP', 987654), 66 | 67 | ]; 68 | -------------------------------------------------------------------------------- /database/migrations/2023_05_28_144254_create_otps_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->morphs('model'); 17 | $table->string('receiver'); 18 | $table->string('otp'); 19 | $table->timestamp('expired_at')->useCurrent(); 20 | $table->timestamp('used_at')->nullable(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists(config('otp.table', 'otps')); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2024_09_27_025709_add_event_to_otps_table.php: -------------------------------------------------------------------------------- 1 | string('event')->after('otp')->nullable(); 16 | $table->bigInteger('model_id')->nullable()->change(); 17 | $table->string('model_type')->nullable()->change(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table(config('otp.table', 'otps'), function (Blueprint $table) { 27 | $table->dropColumn('event'); 28 | $table->bigInteger('model_id')->change(); 29 | $table->string('model_type')->change(); 30 | }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/Exceptions/OtpExpiredException.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__ . '/../config/otp.php', 'otp'); 21 | } 22 | 23 | /** 24 | * Bootstrap services, including configuration publishing and migrations. 25 | * 26 | * @return void 27 | */ 28 | public function boot(): void 29 | { 30 | $this->registerPublishing(); 31 | $this->registerMigrations(); 32 | } 33 | 34 | /** 35 | * Configure publishing of config and migrations for the package. 36 | * 37 | * @return void 38 | */ 39 | private function registerPublishing(): void 40 | { 41 | if ($this->app->runningInConsole()) { 42 | $this->publishes([ 43 | __DIR__ . '/../config/otp.php' => config_path('otp.php'), 44 | ], 'otp-config'); 45 | 46 | $this->publishes([ 47 | __DIR__ . '/../database/migrations' => database_path('migrations'), 48 | ], 'otp-migrations'); 49 | } 50 | } 51 | 52 | /** 53 | * Register the package's migrations. 54 | * 55 | * @return void 56 | */ 57 | private function registerMigrations(): void 58 | { 59 | if ($this->app->runningInConsole()) { 60 | $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Models/Otp.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | protected $fillable = [ 22 | 'model_id', 23 | 'model_type', 24 | 'receiver', 25 | 'otp', 26 | 'expired_at', 27 | 'used_at', 28 | 'event', 29 | ]; 30 | 31 | /** 32 | * Get the OTP value. 33 | * 34 | * @return string OTP string. 35 | */ 36 | public function getOtp(): string 37 | { 38 | return $this->otp; 39 | } 40 | 41 | /** 42 | * Verify the provided OTP. 43 | * 44 | * @param string $otp The OTP to verify. 45 | * @throws OtpExpiredException If the OTP is expired. 46 | * @throws OtpInvalidException If the OTP is invalid. 47 | * @return bool True if the OTP is valid. 48 | */ 49 | public function verifyOtp(string $otp): bool 50 | { 51 | if (Carbon::now()->greaterThan($this->expired_at)) { 52 | throw new OtpExpiredException(__('OTP Expired')); 53 | } 54 | 55 | if ($this->otp !== $otp) { 56 | throw new OtpInvalidException(__('Invalid OTP')); 57 | } 58 | 59 | $this->update(['used_at' => Carbon::now()]); 60 | 61 | return true; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Otp.php: -------------------------------------------------------------------------------- 1 | receiver = $receiver; 53 | $this->length = config('otp.length', 6); 54 | $this->expiry = config('otp.expiry', 5); 55 | $this->format = config('otp.format', 'numeric'); 56 | } 57 | 58 | /** 59 | * Creates an instance for the given receiver. 60 | * 61 | * @param mixed $receiver The receiver identifier. 62 | * @return self New Otp instance. 63 | */ 64 | public static function for(mixed $receiver): self 65 | { 66 | return new self($receiver); 67 | } 68 | 69 | /** 70 | * Sets the OTP expiry time. 71 | * 72 | * @param int $expiry Expiry time in minutes. 73 | * @return self Current instance. 74 | */ 75 | public function setExpiry(int $expiry): self 76 | { 77 | if ($expiry > 0) { 78 | $this->expiry = $expiry; 79 | } 80 | return $this; 81 | } 82 | 83 | /** 84 | * Sets the OTP length. 85 | * 86 | * @param int $length Length of the OTP. 87 | * @return self Current instance. 88 | */ 89 | public function setLength(int $length): self 90 | { 91 | if ($length > 0) { 92 | $this->length = $length; 93 | } 94 | return $this; 95 | } 96 | 97 | /** 98 | * Sets a default OTP value. 99 | * 100 | * @param string|null $otp Custom OTP value. 101 | * @return self Current instance. 102 | */ 103 | public function setDefault(?string $otp = null): self 104 | { 105 | $this->otp = $otp ?? config('otp.default_otp'); 106 | return $this; 107 | } 108 | 109 | /** 110 | * Sets the OTP format. 111 | * 112 | * @param string $format OTP format (alpha, alphanumeric, numeric). 113 | * @return self Current instance. 114 | */ 115 | public function setFormat(string $format): self 116 | { 117 | if (in_array($format, $this->availableFormats, true)) { 118 | $this->format = $format; 119 | } 120 | return $this; 121 | } 122 | 123 | /** 124 | * Checks if a valid OTP exists for the receiver. 125 | * 126 | * @return OtpModel|null Existing OTP model if found, otherwise null. 127 | */ 128 | public function checkOtp(): ?OtpModel 129 | { 130 | return OtpModel::query() 131 | ->where('receiver', $this->receiver) 132 | ->where('expired_at', '>=', now()) 133 | ->whereNull('used_at') 134 | ->first(); 135 | } 136 | 137 | /** 138 | * Generates and saves a new OTP for the receiver. 139 | * If an active OTP exists, it returns the existing OTP. 140 | * 141 | * @param string|null $event Event identifier (optional). 142 | * @return OtpModel OTP model instance. 143 | */ 144 | public function create(?string $event = null): OtpModel 145 | { 146 | if ($otp = $this->checkOtp()) { 147 | return $otp; 148 | } 149 | 150 | return OtpModel::create([ 151 | 'receiver' => $this->receiver, 152 | 'event' => $event, 153 | 'otp' => $this->generateOtp(), 154 | 'expired_at' => now()->addMinutes($this->expiry), 155 | ]); 156 | } 157 | 158 | /** 159 | * Generates and saves a new OTP for the receiver. 160 | * If an active OTP exists, it returns the existing OTP. 161 | * 162 | * @param string|null $event Event identifier (optional). 163 | * @return OtpModel OTP model instance. 164 | */ 165 | public function generate(?string $event = null): OtpModel 166 | { 167 | if ($otp = $this->checkOtp()) { 168 | return $otp; 169 | } 170 | 171 | return new OtpModel([ 172 | 'receiver' => $this->receiver, 173 | 'event' => $event, 174 | 'otp' => $this->generateOtp(), 175 | 'expired_at' => now()->addMinutes($this->expiry), 176 | ]); 177 | } 178 | 179 | /** 180 | * Generates an OTP based on the selected format. 181 | * 182 | * @return mixed Generated OTP. 183 | */ 184 | protected function generateOtp(): mixed 185 | { 186 | if ($this->otp) { 187 | return $this->otp; 188 | } 189 | 190 | if (app()->environment() === 'local') { 191 | return config('otp.default_otp', '123456'); 192 | } 193 | 194 | return match ($this->format) { 195 | 'alpha' => substr(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, $this->length), 196 | 'alphanumeric' => Str::upper(Str::random($this->length)), 197 | default => random_int(pow(10, $this->length - 1), pow(10, $this->length) - 1), 198 | }; 199 | } 200 | 201 | /** 202 | * Retrieves the latest unused OTP for the receiver. 203 | * 204 | * @throws OtpInvalidException If no valid OTP is found. 205 | * @return OtpModel OTP model instance. 206 | */ 207 | public function getOtp(): OtpModel 208 | { 209 | $otp = OtpModel::query() 210 | ->where('receiver', $this->receiver) 211 | ->whereNull('used_at') 212 | ->latest() 213 | ->first(); 214 | 215 | if (!$otp) { 216 | throw new OtpInvalidException(__('Invalid OTP')); 217 | } 218 | 219 | return $otp; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Traits/Otpable.php: -------------------------------------------------------------------------------- 1 | morphMany(Otp::class, 'model'); 31 | } 32 | 33 | /** 34 | * Creates and associates an OTP with the model. 35 | * 36 | * @param Otp $otp The OTP model instance. 37 | * @return Otp The saved OTP instance. 38 | */ 39 | public function createOtp(Otp $otp): Otp 40 | { 41 | return isset($otp->id) ? $otp : $this->otps()->save($otp); 42 | } 43 | 44 | /** 45 | * Verifies an OTP. 46 | * 47 | * @param Otp $otpModel The OTP model instance. 48 | * @param string $otp The OTP value to verify. 49 | * @throws OtpExpiredException If the OTP has expired. 50 | * @throws OtpInvalidException If the OTP is invalid. 51 | * @return bool True if OTP is valid, false otherwise. 52 | */ 53 | public function verifyOtp(Otp $otpModel, string $otp): bool 54 | { 55 | return $otpModel->verifyOtp($otp); 56 | } 57 | } 58 | --------------------------------------------------------------------------------