OTP Authentication in Laravel & Vue.js for Secure Transactions
Introduction
In today’s digital world, security is paramount, especially when dealing with sensitive data like user authentication and financial transactions. One of the most effective ways to enhance security is by implementing One-Time Password (OTP) authentication. This article explores how to implement OTP authentication in a Laravel backend with
OTP Authentication in Laravel & Vue.js for Secure Transactions
Ojekudo Oghenemaro Emmanuel
Introduction
In today’s digital world, security is paramount, especially when dealing with sensitive data like user authentication and financial transactions. One of the most effective ways to enhance security is by implementing One-Time Password (OTP) authentication. This article explores how to implement OTP authentication in a Laravel backend with a Vue.js frontend, ensuring secure transactions.
Why Use OTP Authentication?
OTP authentication provides an extra layer of security beyond traditional username and password authentication. Some key benefits include:
- Prevention of Unauthorized Access: Even if login credentials are compromised, an attacker cannot log in without the OTP.
- Enhanced Security for Transactions: OTPs can be used to confirm high-value transactions, preventing fraud.
- Temporary Validity: Since OTPs expire after a short period, they reduce the risk of reuse by attackers.
Prerequisites
Before getting started, ensure you have the following:
- Laravel 8 or later installed
- Vue.js configured in your project
- A mail or SMS service provider for sending OTPs (e.g., Twilio, Mailtrap)
- Basic understanding of Laravel and Vue.js
In this guide, we’ll implement OTP authentication in a Laravel (backend) and Vue.js (frontend) application. We’ll cover:
- Setting up Laravel and Vue (frontend) from scratch
- Setting up OTP generation and validation in Laravel
- Creating a Vue.js component for OTP input
- Integrating OTP authentication into login workflows
- Enhancing security with best practices
By the end, you’ll have a fully functional OTP authentication system ready to enhance the security of your fintech or web application.
Setting Up Laravel for OTP Authentication
Step 1: Install Laravel and Required Packages
If you haven't already set up a Laravel project, create a new one:
composer create-project "laravel/laravel:^10.0" example-app
Next, install the Laravel Breeze package for frontend scaffolding:
composer require laravel/breeze --dev
After composer has finished installing, run the following command to select the framework you want to use—the Vue configuration:
php artisan breeze:install
You’ll see a prompt with the available stacks:
Which Breeze stack would you like to install?
- Vue with Inertia
Would you like any optional features?
- None
Which testing framework do you prefer?
- PHPUnit
Breeze will automatically install the necessary packages for your Laravel Vue project. You should see:
INFO Breeze scaffolding installed successfully.
Now run the npm command to build your frontend assets:
npm run dev
Then, open another terminal and launch your Laravel app:
php artisan serve
Step 2: Setting up OTP generation and validation in Laravel
We'll use a mail testing platform called Mailtrap to send and receive mail locally. If you don’t have a mail testing service set up, sign up at Mailtrap to get your SMTP credentials and add them to your .env file:
MAIL_MAILER=smtp
MAIL_HOST=sandbox.smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=1780944422200a
MAIL_PASSWORD=a8250ee453323b
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="${APP_NAME}"
To send OTPs to users, we’ll use Laravel’s built-in mail services. Create a mail class and controller:
php artisan make:mail OtpMail
php artisan make:controller OtpController
Then modify the OtpMail class:
Free eBook: Git Essentials
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class OtpMail extends Mailable
use Queueable, SerializesModels;
public $otp;
/**
* Create a new message instance.
public function __construct($otp)
$this->otp = $otp;
/**
* Build the email message.
public function build()
return $this->subject('Your OTP Code')
->view('emails.otp')
->with(['otp' => $this->otp]);
/**
* Get the message envelope.
public function envelope(): Envelope
return new Envelope(
subject: 'OTP Mail',
Create a Blade view in resources/views/emails/otp.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Your OTP Code</title>
</head>
<body>
<p>Hello,</p>
<p>Your One-Time Password (OTP) is: <strong>{{ $otp }}</strong></p>
<p>This code is valid for 10 minutes. Do not share it with anyone.</p>
<p>Thank you!</p>
</body>
</html>
Step 3: Creating a Vue.js component for OTP input
Normally, after login or registration, users are redirected to the dashboard. In this tutorial, we add an extra security step that validates users with an OTP before granting dashboard access.
Create two Vue files:
Request.vue: requests the OTP
Verify.vue: inputs the OTP for verification
Now we create the routes for the purpose of return the View and the functionality of creating OTP codes, storing OTP codes, sending OTP codes through the mail class, we head to our web.php file:
Route::middleware('auth')->group(function () {
Route::get('/request', [OtpController::class, 'create'])->name('request');
Route::post('/store-request', [OtpController::class, 'store'])->name('send.otp.request');
Route::get('/verify', [OtpController::class, 'verify'])->name('verify');
Route::post('/verify-request', [OtpController::class, 'verify_request'])->name('verify.otp.request');
});
Putting all of this code in the OTP controller returns the View for our request.vue and verify.vue file and the functionality of creating OTP codes, storing OTP codes, sending OTP codes through the mail class and verifying OTP codes, we head to our web.php file to set up the routes.
public function create(Request $request)
return Inertia::render('Request', [
'email' => $request->query('email', ''),
]);
public function store(Request $request)
$request->validate([
'email' => 'required|email|exists:users,email',
]);
$otp = rand(100000, 999999);
Cache::put('otp_' . $request->email, $otp, now()->addMinutes(10));
Log::info("OTP generated for " . $request->email . ": " . $otp);
Mail::to($request->email)->send(new OtpMail($otp));
return redirect()->route('verify', ['email' => $request->email]);
public function verify(Request $request)
return Inertia::render('Verify', [
'email' => $request->query('email'),
]);
public function verify_request(Request $request)
$request->validate([
'email' => 'required|email|exists:users,email',
'otp' => 'required|digits:6',
]);
$cachedOtp = Cache::get('otp_' . $request->email);
Log::info("OTP entered: " . $request->otp);
Log::info("OTP stored in cache: " . ($cachedOtp ?? 'No OTP found'));
if (!$cachedOtp) {
return back()->withErrors(['otp' => 'OTP has expired. Please request a new one.']);
if ((string) $cachedOtp !== (string) $request->otp) {
return back()->withErrors(['otp' => 'Invalid OTP. Please try again.']);
Cache::forget('otp_' . $request->email);
$user = User::where('email', $request->email)->first();
if ($user) {
$user->email_verified_at = now();
$user->save();
return redirect()->route('dashboard')->with('success', 'OTP Verified Successfully!');
Having set all this code, we return to the request.vue file to set it up.
[...]