Hi Brothers ,
In this post, we will learn laravel login with otp. let’s discuss about laravel passport login with otp. This article will give you a simple example of laravel login with mobile number otp. This article goes in detailed on laravel login using otp.
Laravel provides basic authentication using email and passwords. But if you want to add a mobile number OTP-based login then how you will be able to do that?, I will show you how to login with OTP in laravel.
In this example, we will install Laravel UI for basic authentication. When the user will register we will ask for a mobile number. Then we will add a button for login with a mobile number OTP in laravel. When the user enters the mobile number and will receive otp on the mobile number. The user will input OTP and login with that. we will send OTP(One Time Password) in SMS using Twilio API.
So, let's see the following step to do this example. You can login with otp in laravel 6, laravel 7, laravel 8, laravel 9, laravel 10 and laravel 11 versions.
Step for Laravel Authentication OTP with Mobile Phone TutorialCommand Line
composer create-project laravel/laravel example-app
After successfully installing the laravel app then after configuring the database setup. We will open the ".env" file and change the database name, username and password in the env file.
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=Enter_Your_Database_Name
DB_USERNAME=Enter_Your_Database_Username
DB_PASSWORD=Enter_Your_Database_Password
Laravel's laravel/ui package provides a quick way to scaffold all of the routes and views you need for authentication using a few simple commands:
Command Line
composer require laravel/ui
Command Line
php artisan ui bootstrap --auth
Then, install npm packages using the below command:
npm install
built bootstrap CSS using the below command:
npm run build
In this step, we need to create two new migration to add mobile_no field to users table and create new table user_otps. so let's run the below code and run migration.
Command Line
php artisan make:migration add_new_fields_users
database/migrations/2022_11_24_110854_add_new_fields_users.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('mobile_no')->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('mobile_no');
});
}
};
Command Line
php artisan make:migration create_user_otps_table
database/migrations/2022_11_24_110854_create_user_otps_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('user_otps', function (Blueprint $table) {
$table->id();
$table->bigInteger('user_id');
$table->string('otp');
$table->timestamp('expire_at')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('user_otps');
}
};
Now, run migration with following command:
Command Line
php artisan migrate
In this step, we will update User.php model and create new model call UserOtp.php. Let's update code for that.
Next, update User.php model file.
app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
'mobile_no'
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
}
app/Models/UserOtp.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Exception;
use Twilio\Rest\Client;
class UserOtp extends Model
{
use HasFactory;
protected $fillable = ['user_id', 'otp', 'expire_at'];
public function sendSMS($receiverNumber)
{
$message = "Login OTP is ".$this->otp;
try {
$account_sid = getenv("TWILIO_SID");
$auth_token = getenv("TWILIO_TOKEN");
$twilio_number = getenv("TWILIO_FROM");
$client = new Client($account_sid, $auth_token);
$client->messages->create($receiverNumber, [
'from' => $twilio_number,
'body' => $message]);
info('SMS Sent Successfully.');
} catch (Exception $e) {
info("Error: ". $e->getMessage());
}
}
}
In this step, we will create new routes for opt and submit otp code. you can see the below routes:
routes/web.php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::controller(App\Http\Controllers\Auth\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');
});
Here, we will create AuthOtpController with some methods and update register controller file as well. so let's copy the below code and add to controller file:
app/Http/Controllers/Auth/RegisterController.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;
protected $redirectTo = RouteServiceProvider::HOME;
public function __construct()
{
$this->middleware('guest');
}
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'mobile_no' => ['required', 'numeric', 'digits:10'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'mobile_no' => $data['mobile_no'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}
app/Http/Controllers/Auth/AuthOtpController.php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
use App\Models\UserOtp;
class AuthOtpController extends Controller
{
public function login()
{
return view('auth.otpLogin');
}
public function generate(Request $request)
{
$request->validate([
'mobile_no' => 'required|exists:users,mobile_no'
]);
$userOtp = $this->generateOtp($request->mobile_no);
$userOtp->sendSMS($request->mobile_no);
return redirect()->route('otp.verification', ['user_id' => $userOtp->user_id])
->with('success', "OTP has been sent on Your Mobile Number.");
}
public function generateOtp($mobile_no)
{
$user = User::where('mobile_no', $mobile_no)->first();
$userOtp = UserOtp::where('user_id', $user->id)->latest()->first();
$now = now();
if($userOtp && $now->isBefore($userOtp->expire_at)){
return $userOtp;
}
return UserOtp::create([
'user_id' => $user->id,
'otp' => rand(123456, 999999),
'expire_at' => $now->addMinutes(10)
]);
}
public function verification($user_id)
{
return view('auth.otpVerification')->with([
'user_id' => $user_id
]);
}
public function loginWithOtp(Request $request)
{
$request->validate([
'user_id' => 'required|exists:users,id',
'otp' => 'required'
]);
$userOtp = UserOtp::where('user_id', $request->user_id)->where('otp', $request->otp)->first();
$now = now();
if (!$userOtp) {
return redirect()->back()->with('error', 'Your OTP is not correct');
}else if($userOtp && $now->isAfter($userOtp->expire_at)){
return redirect()->route('otp.login')->with('error', 'Your OTP has been expired');
}
$user = User::whereId($request->user_id)->first();
if($user){
$userOtp->update([
'expire_at' => now()
]);
Auth::login($user);
return redirect('/home');
}
return redirect()->route('otp.login')->with('error', 'Your Otp is not correct');
}
}
In this step, we will create new blade file for otpLogin and otpVerification, then we will update login and register page as well. so let's update following file:
resources/views/auth/otpLogin.blade.php
@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 action="{{ route('otp.generate') }}" method="POST">@csrf
<div class="row mb-3"><label class="col-md-4 col-form-label text-md-end" for="mobile_no">{{ __('Mobile No') }}</label>
<div class="col-md-6"><input autocomplete="mobile_no" autofocus="" class="form-control @error('mobile_no') is-invalid @enderror" id="mobile_no" name="mobile_no" placeholder="Enter Your Registered Mobile Number" required="" type="text" value="{{ old('mobile_no') }}" /> @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 class="btn btn-primary" type="submit">{{ __('Generate OTP') }}</button>@if (Route::has('login')) <a class="btn btn-link" href="{{ route('login') }}"> {{ __('Login With Email') }} </a> @endif</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
resources/views/auth/otpVerification.blade.php
@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
Now, we need to update login and register view files:
resources/views/auth/login.blade.php
@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">{{ __('Login') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('login') }}">
@csrf
<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" autofocus>
@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="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>
OR
<a class="btn btn-success" href="{{ route('otp.login') }}">
Login with OTP
</a>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
resources/views/auth/register.blade.php
@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="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="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="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
First you need to create and add phone number. then you can easily get account SID, Token and Number.
Create Account from here: www.twilio.com.
Next add Twilio Phone Number
Next you can get account SID, Token and Number and add on .env file as like bellow:
.env
TWILIO_SID=XXXXXXXXXXXXXXXXX
TWILIO_TOKEN=XXXXXXXXXXXXX
TWILIO_FROM=+XXXXXXXXXXX
next, we need to install twilio/sdk composer package to use send SMS using twilio. so let's run bellow command:
Command Line
composer require twilio/sdk
All the required steps have been done, now you have to type the given below command and hit enter to run the Laravel app:
php artisan serve