walkweltech / laravel-otp
Secure your laravel routes with otps. (one time passwords)
Requires
- php: ^7.2.5
- illuminate/database: ^7.0|^8.0
- illuminate/http: ^7.0|^8.0
- illuminate/notifications: ^7.0|^8.0
- illuminate/routing: ^7.0|^8.0
- illuminate/support: ^7.0|^8.0
- nesbot/carbon: ^2.35
Requires (Dev)
- illuminate/config: ^7.0|^8.0
- mockery/mockery: ~1.3.1
- phpunit/phpunit: 7.*
README
This is a fork of original because at the point of inception original did not had Laravel 8 Support. Also new features as they come are planned to be added into this fork.
This package allows you to secure your resources with one time password access (otp).
Example Usage:
Route::get('secret', function (\Illuminate\Http\Request $request): string { $token = $request->otpToken(); $messages[] = "The otp token {$token} has {$token->timeLeft()} out of {$token->expiryTime()} seconds."; $token->refresh(); $messages[] = "The time left can be reset using the refresh method: {$token->timeLeft()}/{$token->expiryTime()}"; $token->extend(30); $messages[] = "The expiry time can be increased using the extend method: {$token->timeLeft()}/{$token->expiryTime()}"; $messages[] = "You can also invalidate the token immediately. Try refreshing the page ;)"; $request->otpToken()->invalidate(); return implode('<br>', $messages); })->middleware('auth', 'otp');
Contents
Installation
1- Add the package to your dependencies.
$ composer require walkweltech/laravel-otp;
2- Register the package in your config/app.php
file:
only if you your auto package discovery off.
WalkwelTech\Otp\OtpServiceProvider::class,
3- Publish the components:
Publishes a migration, two views and a configuration file.
$ php artisan vendor:publish
4- Apply the migrations:
Will create a table called otp_tokens
to store generated token information.
$ php artisan migrate
5- Register the routes:
These routes are required if you are planning to use otp
middleware.
In your RouteServiceProvider, append the following line inside the map
method:
// App\RouteServiceProvider@map: \WalkwelTech\Otp\OtpRoutes::register();
6- Register the route middleware:
Register the otp route middleware inside your App\Http\Kernel
.
/** * The application's route middleware. * * These middleware may be assigned to groups or used individually. * * @var array */ protected $routeMiddleware = [ // [...] 'otp' => \WalkwelTech\Otp\Http\Middleware\Otp::class, ];
Configuration
This package comes with a set of handy configuration options:
password_generator: The password generator option allows you to decide which generator implementation to be used to generate new passwords.
Available built-in options: string, numeric and numeric-no-0. default: string
table: The name of the table to be used to store the otp tokens.
default: otp_tokens
expiry_time: The expiry time of the tokens in minutes.
default: 15
default_channels: The default notification channels of the token notification.
Usage
Basic Usage
After configuring your instance of the package,
you can use the built-in otp
middleware alias to secure your endpoints:
Route::get('secret', function (Request $request): string { $request->otpToken()->refresh(); return 'The secret of immortality'; })->middleware('auth', 'otp');
This middleware will redirect any unauthenticated request to the otp/create
endpoint
which we have registered in the installation process:
- A password will be generated using the configured password generator.
- The authenticated user will be notified about the password via the configured notification channel.
- The user will see a form to submit their password.
- You can change the appearance of the view under your
resources/views/otp
directory, modifyingcreate.blade.php
file. - After a successful authentication; the user will be redirected back to the original route they requested at the first step.
- The redirected request will also include the
otpToken()
instance being used by the user.
Advanced Usage
Adding the notification channel method:
If you are not using the mail
channel, or your notification channel is expecting a method different than mail
or sms
, you can register your own method like:
// AppServiceProvider::register(): TokenNotification::macro('AcmeSms', function () { // $this is TokenNotification class. return $this->notification->code; });
Don't forget to change your configuration file as well.
Using your own password generator:
To add your own password generator implemetation, you can call addPasswordGenerator
method on Otp
service like:
// AppServiceProvider::register(): app('otp')->addPasswordGenerator('acme', function (int $length): string { return 'your_implementation'; });
If you need more power, you can also create your own password generator class too:
<?php namespace App\Acme\PasswordGenerators; use WalkwelTech\Otp\PasswordGeneratorInterface; class AcmePasswordGenerator implements PasswordGeneratorInterface { /** * Generate an acme password with the given length. * * @param int $length * @return string */ public function generate(int $length): string { return 'your implementation'; } }
You can register you password generator like the previous one:
// AppServiceProvider::register(): Otp::addPasswordGenerator('acme', AcmePasswordGenerator::class);
Don't forget to change your configuration file as well.
Determining the otp channel per notifiable
The Notification
class checks otpChannels
existence inside the notifiable
being notified.
If so, this method is being called to determine which notification channel is going to be used to notify the notifiable.
Deeper Knowledge:
The public API consists of two main components: OtpService
and the Token
which generally is being returned by the service.
Otp Service:
If you are planning to create your own API or the basic functionality is not enough four you, you can use the Otp Service API:
Chencking the validity of a given token:
$isTokenValid = Otp::check($authenticableId, $token);
Setting the password generator:
Otp::setPasswordGenerator('string');
Creating a new token for a given user:
$token = Otp::create(auth()->user(), $length = 6); // See what can be done with tokens below.
Retrieveing an existing token from the storage by the given plain password:
$token = Otp::retrieveByPlainText(auth()->id(), $otpPassword); // See what can be done with tokens below.
Retrieveing an existing token from the storage by the given cipher text (token):
$token = Otp::retrieveByCipherText(auth()->id(), $otpPassword); // See what can be done with tokens below.
Changing the behavior of the Service
The package comes with a ServiceProvider
which registers the Otp
service to your application's container.
The Otp orchestrates the method calls made to the 3 interface implementations below.
- PasswordGeneratorManagerInterface
- EncryptorInterface and
- TokenInterface
You can write your service provider and register the OtpService
with your version of the dependencies.
Note: Because the token class is being used with static calls, you have to send the fully qualified name of your TokenInterface implementation.
Token API:
Getting the attributes of the token:
public function authenticableId(); public function cipherText(): string; public function plainText(): ?string; // If you have just created the token, plain text will be accessable. If you retrieved it; it won't. public function createdAt(): Carbon; public function updatedAt(): Carbon; public function expiryTime(): int; public function expiresAt(): Carbon; public function timeLeft(): int; public function expired(): bool;
Invalidate a token:
public function revoke(): void; public function invalidate(): void;
e.g.
public function show(Request $request, $id) { if($request->input('revoke_session', false)) { $request->otpToken()->revoke(); } return view('heaven'); }
Extend or refresh the token expiry time:
// Extend the usage time of the token for the given seconds: public function extend(?int $seconds = null): bool; // Make the token function like it has just been created: public function refresh(): bool;
e.g.
$token = Otp::retrieveByCipherText( auth()->id(), $request->input('otp_token') ); if(! $token->expired()) { $token->refresh(); }
Create a new token:
public static function create( $authenticableId, string $cipherText, ?string $plainText = null ): TokenInterface;
e.g.
$token = Token::create(1, 'foo', 'plain foo');
Retrieve a token from the storage by the given attributes:
Make sure that the attributes you provided will return a unique token.
public static function retrieveByAttributes(array $attributes): ?TokenInterface;
e.g.
$token = Token::retrieveByAttributes([ 'authenticable_id' => 1, 'cipher_text' => 'foo', ]);
Convert the token to a notification:
public function toNotification(): Notification;
e.g.
$user->notify($token->toNotification());
Changelog
Please see CHANGELOG for more information what has changed recently.
Testing
$ composer test
Credits
- Hilmi Erdem KEREN
- Berkay Güre