In this post, I am going to explain about login with OTP in LARAVEL 9.
After Installing Laravel and Laravel Authentication open code project in code editor & follow the steps below.
Step - 1
Add Mobile Number in users
table
To add a column, create a Migration by running command below.
php artisan make:migration add_mobile_no_in_users_table
The Command will generate a file in database/migrations
folder, open the migration file and update the code below.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('mobile_no')->nullable()->after('username');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('mobile_no');
});
}
};
Now run the migration by command, it will add mobile number in users table.
php artisan migrate
Now Update the app/Http/Controllers/Auth/RegisterController.php
to register a user with mobile Number.
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'username' => ['required', 'string', 'max:255'],
'mobile_no' => ['required', 'number', 'max:10'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\Models\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'username' => $data['username'],
'mobile_no' => $data['mobile_no'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}
Next update the 'resources/views/auth/register.blade.php' and update code below.
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Register') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('register') }}">
@csrf
<div class="row mb-3">
<label for="name" class="col-md-4 col-form-label text-md-end">{{ __('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="username" class="col-md-4 col-form-label text-md-end">{{ __('Username') }}</label>
<div class="col-md-6">
<input id="username" type="text" class="form-control @error('username') is-invalid @enderror" name="username" value="{{ old('username') }}" required autocomplete="username" autofocus>
@error('username')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="mobile_no" class="col-md-4 col-form-label text-md-end">{{ __('Mobile No') }}</label>
<div class="col-md-6">
<input id="mobile_no" type="text" class="form-control @error('mobile_no') is-invalid @enderror" name="mobile_no" value="{{ old('mobile_no') }}" required autocomplete="mobile_no" autofocus>
@error('mobile_no')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password-confirm" class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Register') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
The Register form will look like.
Step - 2
Create VerificationCode Model with Migration by running Command below.
php artisan make:model VerificationCode -m
This command will create a VerificationCode model in app/models
folder and a migration in database/migrations
.
Let's open the VerificationCode Model from app/Models/VerificationCode.php
& update with code below.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class VerificationCode extends Model
{
use HasFactory;
protected $fillable = ['user_id', 'otp', 'expire_at'];
}
Now update migration of verification_codes
table, open file from database/migrations
and update the code below.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('verification_codes', function (Blueprint $table) {
$table->id();
$table->bigInteger('user_id');
$table->string('otp');
$table->timestamp('expire_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('verification_codes');
}
};
Next, run the migration to create verification_codes
table.
php artisan migrate
Step - 3
Create AuthOtpController by running command
php artisan make:controller AuthOtpController
This controller will handle all the operation related to Authentication using OTP.
Open the controller from app/Http/Controllers/AuthOtpController.php
& Update the controller by the code below.
<?php
namespace App\Http\Controllers;
use Carbon\Carbon;
use App\Models\User;
use Illuminate\Http\Request;
use App\Models\VerificationCode;
use Illuminate\Support\Facades\Auth;
class AuthOtpController extends Controller
{
// Return View of OTP Login Page
public function login()
{
return view('auth.otp-login');
}
// Generate OTP
public function generate(Request $request)
{
# Validate Data
$request->validate([
'mobile_no' => 'required|exists:users,mobile_no'
]);
# Generate An OTP
$verificationCode = $this->generateOtp($request->mobile_no);
$message = "Your OTP To Login is - ".$verificationCode->otp;
# Return With OTP
return redirect()->route('otp.verification', ['user_id' => $verificationCode->user_id])->with('success', $message);
}
public function generateOtp($mobile_no)
{
$user = User::where('mobile_no', $mobile_no)->first();
# User Does not Have Any Existing OTP
$verificationCode = VerificationCode::where('user_id', $user->id)->latest()->first();
$now = Carbon::now();
if($verificationCode && $now->isBefore($verificationCode->expire_at)){
return $verificationCode;
}
// Create a New OTP
return VerificationCode::create([
'user_id' => $user->id,
'otp' => rand(123456, 999999),
'expire_at' => Carbon::now()->addMinutes(10)
]);
}
public function verification($user_id)
{
return view('auth.otp-verification')->with([
'user_id' => $user_id
]);
}
public function loginWithOtp(Request $request)
{
#Validation
$request->validate([
'user_id' => 'required|exists:users,id',
'otp' => 'required'
]);
#Validation Logic
$verificationCode = VerificationCode::where('user_id', $request->user_id)->where('otp', $request->otp)->first();
$now = Carbon::now();
if (!$verificationCode) {
return redirect()->back()->with('error', 'Your OTP is not correct');
}elseif($verificationCode && $now->isAfter($verificationCode->expire_at)){
return redirect()->route('otp.login')->with('error', 'Your OTP has been expired');
}
$user = User::whereId($request->user_id)->first();
if($user){
// Expire The OTP
$verificationCode->update([
'expire_at' => Carbon::now()
]);
Auth::login($user);
return redirect('/home');
}
return redirect()->route('otp.login')->with('error', 'Your Otp is not correct');
}
}
Step - 4
Create Routes for OTP login and Verification, open routes/web.php
and update the code at bottom.
Route::controller(AuthOtpController::class)->group(function(){
Route::get('/otp/login', 'login')->name('otp.login');
Route::post('/otp/generate', 'generate')->name('otp.generate');
Route::get('/otp/verification/{user_id}', 'verification')->name('otp.verification');
Route::post('/otp/login', 'loginWithOtp')->name('otp.getlogin');
});
Step - 5
Create view files in resources/views/auth
folder,
First create otp-login.blade.php
and code below.
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('OTP Login') }}</div>
<div class="card-body">
@if (session('error'))
<div class="alert alert-danger" role="alert"> {{session('error')}}
</div>
@endif
<form method="POST" action="{{ route('otp.generate') }}">
@csrf
<div class="row mb-3">
<label for="mobile_no" class="col-md-4 col-form-label text-md-end">{{ __('Mobile No') }}</label>
<div class="col-md-6">
<input id="mobile_no" type="text" class="form-control @error('mobile_no') is-invalid @enderror" name="mobile_no" value="{{ old('mobile_no') }}" required autocomplete="mobile_no" autofocus placeholder="Enter Your Registered Mobile Number">
@error('mobile_no')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Generate OTP') }}
</button>
@if (Route::has('login'))
<a class="btn btn-link" href="{{ route('login') }}">
{{ __('Login With Username') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
Now create another view file otp-verification.blade.php
& update the code below.
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('OTP Login') }}</div>
<div class="card-body">
@if (session('success'))
<div class="alert alert-success" role="alert"> {{session('success')}}
</div>
@endif
@if (session('error'))
<div class="alert alert-danger" role="alert"> {{session('error')}}
</div>
@endif
<form method="POST" action="{{ route('otp.getlogin') }}">
@csrf
<input type="hidden" name="user_id" value="{{$user_id}}" />
<div class="row mb-3">
<label for="mobile_no" class="col-md-4 col-form-label text-md-end">{{ __('OTP') }}</label>
<div class="col-md-6">
<input id="otp" type="text" class="form-control @error('otp') is-invalid @enderror" name="otp" value="{{ old('otp') }}" required autocomplete="otp" autofocus placeholder="Enter OTP">
@error('otp')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
Complete Flow will look like.
Open the project and go to /otp/login
and it will look like.
As soon as you enter the registered mobile number, it will redirect you to verification screen with OTP in flash message.
After you enter the OTP, It will login and redirect you on dashboard.
I hope this will help login with OTP
in LARAVEL 9.
Complete Video Tutorial on YouTube.
If you face any issues while implementing, please comment your query.
Thank You for Reading
Top comments (7)
Good Job!
There is an OTP Laravel package that allows you to send/resend your OTP notifications to users for authentication.
github.com/mohammad-fouladgar/lara...
Thanks for putting the package.
You're welcome!
Hi,
I followed the step by step and I get the following error.
can you help me?
I think you need to import the AuthOtpController there
Hello, nice article!
Could you please show how to do otp account verification only, so users could login with username or email with password only, once they verify account through OTP. Thanks!
You can check
youtube.com/@TechToolIndia
There are more login options i have explained.