├── .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 |
2 |
LaravelOtp : generate OTP and Validate OTP
3 |
4 | ---
5 |
6 | 
7 | 
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 |
--------------------------------------------------------------------------------