Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      In-House vs. Outsource Node.js Development Teams: 9 Key Differences for the C-Suite (2025)

      July 19, 2025

      Why Non-Native Content Designers Improve Global UX

      July 18, 2025

      DevOps won’t scale without platform engineering and here’s why your teams are still stuck

      July 18, 2025

      This week in AI dev tools: Slack’s enterprise search, Claude Code’s analytics dashboard, and more (July 18, 2025)

      July 18, 2025

      I ditched my Bluetooth speakers for this slick turntable – and it’s more practical than I thought

      July 19, 2025

      This split keyboard offers deep customization – if you’re willing to go all in

      July 19, 2025

      I spoke with an AI version of myself, thanks to Hume’s free tool – how to try it

      July 19, 2025

      I took a walk with Meta’s new Oakley smart glasses – they beat my Ray-Bans in every way

      July 19, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      The details of TC39’s last meeting

      July 19, 2025
      Recent

      The details of TC39’s last meeting

      July 19, 2025

      Simple wrapper for Chrome’s built-in local LLM (Gemini Nano)

      July 19, 2025

      Online Examination System using PHP and MySQL

      July 18, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Top 7 Computer Performance Test Tools Online (Free & Fast)

      July 19, 2025
      Recent

      Top 7 Computer Performance Test Tools Online (Free & Fast)

      July 19, 2025

      10 Best Windows 11 Encryption Software

      July 19, 2025

      Google Chrome Is Testing Dynamic Country Detection for Region-Specific Features

      July 19, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»OTP Authentication in Laravel & Vue.js for Secure Transactions

    OTP Authentication in Laravel & Vue.js for Secure Transactions

    April 20, 2025

    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
    MAIL_FROM_ADDRESS=hello@example.com
    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:

    <?php
    
    namespace AppMail;
    
    use IlluminateBusQueueable;
    use IlluminateContractsQueueShouldQueue;
    use IlluminateMailMailable;
    use IlluminateMailMailablesContent;
    use IlluminateMailMailablesEnvelope;
    use IlluminateQueueSerializesModels;
    
    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.

    <script setup>
    import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
    import InputError from '@/Components/InputError.vue';
    import InputLabel from '@/Components/InputLabel.vue';
    import PrimaryButton from '@/Components/PrimaryButton.vue';
    import TextInput from '@/Components/TextInput.vue';
    import { Head, useForm } from '@inertiajs/vue3';
    
    const props = defineProps({
        email: {
            type: String,
            required: true,
        },
    });
    
    const form = useForm({
        email: props.email,
    });
    
    const submit = () => {
        form.post(route('send.otp.request'), {
            onSuccess: () => {
                alert("OTP has been sent to your email!");
                form.get(route('verify'), { email: form.email }); // Redirecting to OTP verification
            },
        });
    };
    </script>
    
    <template>
        <Head title="Request OTP" />
    
        <AuthenticatedLayout>
            <form @submit.prevent="submit">
                <div>
                    <InputLabel for="email" value="Email" />
    
                    <TextInput
                        id="email"
                        type="email"
                        class="mt-1 block w-full"
                        v-model="form.email"
                        required
                        autofocus
                    />
    
                    <InputError class="mt-2" :message="form.errors.email" />
                </div>
    
                <div class="mt-4 flex items-center justify-end">
                    <PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
                        Request OTP
                    </PrimaryButton>
                </div>
            </form>
        </AuthenticatedLayout>
    </template>
    

    Having set all this code, we return to the verify.vue to set it up:

    <script setup>
    import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
    import InputError from '@/Components/InputError.vue';
    import InputLabel from '@/Components/InputLabel.vue';
    import PrimaryButton from '@/Components/PrimaryButton.vue';
    import TextInput from '@/Components/TextInput.vue';
    import { Head, useForm, usePage } from '@inertiajs/vue3';
    
    const page = usePage();
    // Get the email from the URL query params
    const email = page.props.email || '';
    
    // Initialize form with email and OTP field
    const form = useForm({
        email: email,
        otp: '',
    });
    
    // Submit function
    const submit = () => {
        form.post(route('verify.otp.request'), {
            onSuccess: () => {
                alert("OTP verified successfully! Redirecting...");
                window.location.href = '/dashboard'; // Change to your desired redirect page
            },
            onError: () => {
                alert("Invalid OTP. Please try again.");
            },
        });
    };
    </script>
    
    <template>
        <Head title="Verify OTP" />
    
        <AuthenticatedLayout>
            <form @submit.prevent="submit">
                <div>
                    <InputLabel for="otp" value="Enter OTP" />
    
                    <TextInput
                        id="otp"
                        type="text"
                        class="mt-1 block w-full"
                        v-model="form.otp"
                        required
                    />
    
                    <InputError class="mt-2" :message="form.errors.otp" />
                </div>
    
                <div class="mt-4 flex items-center justify-end">
                    <PrimaryButton :disabled="form.processing">
                        Verify OTP
                    </PrimaryButton>
                </div>
            </form>
        </AuthenticatedLayout>
    </template>
    

    Step 4: Integrating OTP authentication into login and register workflows

    Update the login controller:

    public function store(LoginRequest $request): RedirectResponse
    {
        $request->authenticate();
    
        $request->session()->regenerate();
    
        return redirect()->intended(route('request', absolute: false));
    }
    

    Update the registration controller:

    public function store(Request $request): RedirectResponse
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|lowercase|email|max:255|unique:' . User::class,
            'password' => ['required', 'confirmed', RulesPassword::defaults()],
        ]);
    
        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);
    
        event(new Registered($user));
    
        Auth::login($user);
    
        return redirect(route('request', absolute: false));
    }
    

    Conclusion

    Implementing OTP authentication in Laravel and Vue.js enhances security for user logins and transactions. By generating, sending, and verifying OTPs, we can add an extra layer of protection against unauthorized access. This method is particularly useful for financial applications and sensitive user data.

    Source: Read More

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleAI Ethics and Privacy: The Human Role in Responsible Tech
    Next Article APT29 Deploys GRAPELOADER Malware Targeting European Diplomats Through Wine-Tasting Lures

    Related Posts

    Artificial Intelligence

    Scaling Up Reinforcement Learning for Traffic Smoothing: A 100-AV Highway Deployment

    July 19, 2025
    Repurposing Protein Folding Models for Generation with Latent Diffusion
    Artificial Intelligence

    Repurposing Protein Folding Models for Generation with Latent Diffusion

    July 19, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    ASUS Patches DriverHub RCE Flaws Exploitable via HTTP and Crafted .ini Files

    Development

    Reasons Why Your WordPress Website Isn’t Ranking in Google

    Web Development

    CVE-2025-48888 – Deno Deny-Read Allow-Read Permission Confusion

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-39202 – MicroSCADA X SYS600 File Disclosure and Overwrite Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Highlights

    CVE-2025-5148 – FunAudioLLM InspireMusic Pickle Data Handler Deserialization Vulnerability

    May 25, 2025

    CVE ID : CVE-2025-5148

    Published : May 25, 2025, 12:15 p.m. | 40 minutes ago

    Description : A vulnerability was found in FunAudioLLM InspireMusic up to bf32364bcb0d136497ca69f9db622e9216b029dd. It has been classified as critical. Affected is the function load_state_dict of the file inspiremusic/cli/model.py of the component Pickle Data Handler. The manipulation leads to deserialization. An attack has to be approached locally. This product is using a rolling release to provide continious delivery. Therefore, no version details for affected nor updated releases are available. The name of the patch is 784cbf8dde2cf1456ff808aeba23177e1810e7a9. It is recommended to apply a patch to fix this issue.

    Severity: 5.3 | MEDIUM

    Visit the link for more details, such as CVSS details, affected products, timeline, and more…

    All Pixel Watch 3s are on sale at Best Buy – save $70 on the LTE model

    June 13, 2025

    CVE-2025-52561 – HTMLSanitizer.jl SVG Tag Injection Vulnerability

    June 23, 2025

    Canadese overheid meldt aanval op telecombedrijf via bekend Cisco-lek

    June 23, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.