Handle OTP Auth via your own source code
Ngo Dinh Cuong
Posted on August 17, 2021
Laravel OTP AUTH
This package allows you to authenticate with one time password access (OTP).
https://github.com/cuongnd88/otp-auth
Example Usage:
Route::get("/notify", function(){
return App\Models\User::find(1)->notify(new App\Authentication\SendOtp('mail', 4, 10));
});
Route::get("/auth-otp/{otp}", function(){
return App\Models\User::authByOtp(request()->otp, '84905279285');
});
Route::get("/check-otp/{otp}", function(){
return App\Models\User::find(1)->checkOtp(request()->otp);
});
Contents
Installation
1- Add the package to your dependencies.
$ composer require cuongnd88/otp-auth
2- Run the command:
php artisan auth:otp {ClassName}
Example:
php artisan auth:otp Authentication/SendOtp
SendOtp
class and HasOtpAuth
trait are auto-generated at app/Authentication
directory.
CreateNotificationsTable
class is alseo auto-generated at app/database/migrations
.
3- Apply the migrations:
It will create a table called notifications
to store generated OTP information.
$ php artisan migrate
Usage
Generate OTP
You can generate OTP via email or SMS
Route::get("/notify", function(){
return App\Models\User::find(1)->notify(new App\Authentication\SendOtp(['mail', 'nexmo']));
});
This package allows you to alter OTP length and lifetime
Route::get("/notify", function(){
$length = 4;
$liftime = 10; //minutes
return App\Models\User::find(1)->notify(new App\Authentication\SendOtp(['mail', 'nexmo']), $length, $liftime);
});
OTP default length: The default length is 6
.
OTP default lifetime: The default lifetime is 1
minute.
There is the detail of auto-generate SentOTP
class:
<?php
namespace App\Authentication;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Cuongnd88\DeliveryChannel\Messages\TwilioMessage;
class SendOtp extends Notification
{
use Queueable;
protected $defaultChannels = ['database'];
protected $otp;
protected $lifeTime;
const OPT_LIFETIME = 1;
const OPT_LENGTH = 6;
/**
* Construct
*
* @param array|string $channels
* @param integer|string $otpLength
* @param integer|string $lifeTime
*/
public function __construct($channels = null, $otpLength = null, $lifeTime = null)
{
$this->otp = $this->generateOtp($otpLength ?? self::OPT_LENGTH);
$this->lifeTime = $lifeTime ?? self::OPT_LIFETIME;
$this->defaultChannels = $this->verifyChannels($channels);
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return $this->defaultChannels;
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('Your OTP is '.$this->otp)
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
'otp' => $this->otp,
'expired_at' => now()->addMinutes($this->lifeTime)->toDateTimeString(),
];
}
/**
* Get the Nexmo / SMS representation of the notification.
*
* @param mixed $notifiable
*
* @return mixed
*/
public function toTwilio($notifiable)
{
return (new TwilioMessage)
->to("+8439xxxxxxx")
->from("+xxxxxxxxxx")
->body('OTP AUTH is '.$this->otp);
}
/**
* Generate OTP
*
* @param integer|string $n
*
* @return string
*/
public function generateOtp($n)
{
$generator = "0987654321";
$result = "";
for ($i = 1; $i <= $n; $i++) {
$result .= substr($generator, (rand()%(strlen($generator))), 1);
}
return $result;
}
/**
* Verify channels
*
* @param string|array $channels
*
* @return array
*/
public function verifyChannels($channels)
{
if ($channels && is_array($channels)) {
return array_merge($this->defaultChannels, $channels);
}
if ($channels && is_string($channels)) {
array_push($this->defaultChannels, $channels);
}
return $this->defaultChannels;
}
}
toTwilio: This method is implemented by importing delivery-channels.
Verify OTP
After sent OTP via your configed methods, you call authByOtp
to authenticate
Route::get("/auth-otp/{otp}", function(){
return App\Models\User::authByOtp(request()->otp, '84905123456');
});
Based on your credentials, you might authenticate with email or phone number
Set up the credentials:
In this case, you can apply User
model which must use HasOtpAuth
trait
. . . .
use App\Authentication\HasOtpAuth;
class User extends Authenticatable
{
use Notifiable;
use HasOtpAuth;
protected $credential = 'mobile';
. . . .
Let see more detail HasOtpAuth
trait
<?php
namespace App\Authentication;
trait HasOtpAuth
{
/**
* Check OTP
*
* @return bool
*/
public function checkOtp($otp)
{
$authenticator = $this->otp();
return $this->validateOtp($authenticator, $otp);
}
/**
* Get OTP data
*
* @return \Illuminate\Notifications\DatabaseNotification
*/
public function otp()
{
return $this->notifications()
->where('type', 'LIKE', '%SendOtp%')
->whereNull('read_at')
->first();
}
/**
* Validate OTP
*
* @param \Illuminate\Notifications\DatabaseNotification $authenticator
* @param mixed $otp
*
* @return void
*/
public function validateOtp($authenticator, $otp)
{
$result = false;
if (is_null($authenticator)) {
return response()->json($result,200);
}
if ($authenticator
&& now()->lte($authenticator->data['expired_at'])
&& $authenticator->data['otp'] == $otp
) {
$result = true;
}
$authenticator->markAsRead();
return response()->json($result,200);
}
/**
* Authenticate by OTP
*
* @param string $otp
* @param string $credentialValue
* @return void
*/
public static function authByOtp($otp, $credentialValue)
{
$model = new static;
$credentialName = property_exists($model,'credential') ? $model->credential : 'email';
$authenticator = $model->where($credentialName, '=', $credentialValue)->first();
if (is_null($authenticator)) {
return response()->json(false,200);
}
$authenticator = $authenticator->notifications()
->where('type', 'LIKE', '%SendOtp%')
->whereNull('read_at')
->first();
return $model->validateOtp($authenticator, $otp);
}
}
Basic identification
In some cases, you just need to identify the right access, you might need to execute checkOtp
method
Route::get("/check-otp/{otp}", function(){
return auth()->user->checkOtp(request()->otp);
});
Demo
This is demo soure code.
Laravel Colab
Credits
- Ngo Dinh Cuong
Posted on August 17, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.