Laravel 11 Login with Mobile Number OTP Tutorial

Implementing OTP-based login functionality in a Laravel 11 application simplifies user authentication by adding an extra layer of security. Here’s how you can set up an OTP system using Bootstrap for the user interface, including generating, sending, and validating OTPs:

Step 1: Install Bootstrap UI for Authentication

Begin by setting up Bootstrap UI authentication in your Laravel project. Run these commands to install Bootstrap and configure authentication:

cd your-laravel-project

composer require laravel/ui
php artisan ui bootstrap
php artisan ui:auth

npm install && npm run dev

Step 2: Configure SMS Provider

Update your .env file to include your SMS service provider credentials:

TWILIO_SID=your_twilio_sid
TWILIO_AUTH_TOKEN=your_twilio_auth_token
TWILIO_FROM=your_twilio_phone_number

Step 3: Create the OTP Model and Migration

Generate the OTP model and migration file by running:

php artisan make:model Otp -m

Then, modify the migration file located at database/migrations/create_otps_table.php to include the necessary fields:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateOtpsTable extends Migration
{
public function up()
{
Schema::create('otps', function (Blueprint $table) {
$table->id();
$table->string('mobile');
$table->string('otp');
$table->timestamp('expires_at');
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('otps');
}
}

Update the User.php model to include the mobile field:

protected $fillable = [
'name', 'email', 'password', 'mobile',
];

Run the migration to apply these changes to the database:

php artisan migrate

Step 4: Create OTP Controller

Create a controller to manage OTP generation, sending, and verification:

php artisan make:controller OTPController

In app/Http/Controllers/OTPController.php, implement the following methods:

namespace App\Http\Controllers;

use App\Models\Otp;
use Illuminate\Http\Request;
use Twilio\Rest\Client;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use App\Models\User;

class OTPController extends Controller
{
public function sendOtp(Request $request)
{
$request->validate([
'mobile' => 'required|digits:10'
]);

$otp = rand(100000, 999999);
$expiresAt = Carbon::now()->addMinutes(5);

Otp::updateOrCreate(
['mobile' => $request->mobile],
['otp' => $otp, 'expires_at' => $expiresAt]
);

// Send OTP using Twilio
$twilio = new Client(env('TWILIO_SID'), env('TWILIO_AUTH_TOKEN'));
$twilio->messages->create($request->mobile, [
'from' => env('TWILIO_FROM'),
'body' => 'Your OTP code is ' . $otp
]);

return response()->json(['message' => 'OTP has been sent']);
}

public function verifyOtp(Request $request)
{
$request->validate([
'mobile' => 'required|digits:10',
'otp' => 'required|digits:6'
]);

$otpRecord = Otp::where('mobile', $request->mobile)->where('otp', $request->otp)->first();

if ($otpRecord && Carbon::now()->lessThanOrEqualTo($otpRecord->expires_at)) {
$user = User::firstOrCreate(['mobile' => $request->mobile]);

Auth::login($user);

return redirect()->route('home')->with('message', 'Successfully logged in');
}

return back()->withErrors(['otp' => 'Invalid or expired OTP']);
}
}

Step 5: Define Routes

Add routes for OTP operations in routes/web.php:

use App\Http\Controllers\OTPController;

Route::post('/send-otp', [OTPController::class, 'sendOtp'])->name('send.otp');
Route::post('/verify-otp', [OTPController::class, 'verifyOtp'])->name('verify.otp');

Step 6: Create OTP Views

Create two Blade templates for OTP operations.

For sending OTP (resources/views/auth/otp.blade.php):

<!DOCTYPE html>
<html>
<head>
<title>Login with OTP - Laravel 11</title>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">Request OTP</div>
<div class="card-body">
<form method="POST" action="{{ route('send.otp') }}">
@csrf
<div class="form-group">
<label for="mobile">Mobile Number</label>
<input type="text" name="mobile" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Send OTP</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

For verifying OTP (resources/views/auth/verify.blade.php):

<!DOCTYPE html>
<html>
<head>
<title>Verify OTP - Laravel 11</title>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">Verify OTP</div>
<div class="card-body">
<form method="POST" action="{{ route('verify.otp') }}">
@csrf
<div class="form-group">
<label for="mobile">Mobile Number</label>
<input type="text" name="mobile" class="form-control" required>
</div>
<div class="form-group">
<label for="otp">OTP</label>
<input type="text" name="otp" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Verify OTP</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

Step 7: Run and Test

Launch the Laravel server with:

php artisan serve

Navigate to the following URL in your browser to test the OTP functionality:

http://localhost:8000/login