Get Appointment

Blog Single

How to Set Up Google Authenticator 2FA in Laravel: A Step-by-Step Guide

  • Vfix Technology
  • 21 May 2025
  • Laravel
  • 173 Views

Two-Factor Authentication (2FA) adds an extra layer of security to your Laravel application by requiring users to verify their identity using a time-based one-time password (TOTP). Google Authenticator is a popular choice for implementing 2FA.

In this guide, we'll walk through setting up Google Authenticator 2FA in Laravel using the pragmarx/google2fa-laravel package.

Step 1: Install Laravel

First, create a new Laravel project:

composer create-project laravel/laravel 2fa
cd 2fa

🚫 Step 2: Skip Initial Migration

We will add a custom column first.

Edit the users migration file (database/migrations/...create_users_table.php):

$table->text('google2fa_secret')->nullable();

💾 Step 3: Run Migration:

Execute the migration to update the database:

php artisan migrate:fresh

Step 4: Install Laravel UI

Set up basic authentication scaffolding:

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

Step 5: Compile Frontend Assets

Install and build frontend dependencies:

npm install && npm run build

Step 6: Install Google2FA Package

Install the Google2FA Laravel package:

composer require pragmarx/google2fa-laravel

Step 7: Install QR Code Generator

We'll need bacon/bacon-qr-code for generating QR codes:

composer require bacon/bacon-qr-code

Step 8: Publish Package Config

Publish the Google2FA configuration file:

php artisan vendor:publish --provider="PragmaRX\Google2FALaravel\ServiceProvider"

Step 9: Configure Google2FA

Update config/google2fa.php with the following:

<?php

return [
    'enabled' => env('OTP_ENABLED', true),
    'lifetime' => env('OTP_LIFETIME', 0),
    'keep_alive' => env('OTP_KEEP_ALIVE', true),
    'auth' => 'auth',
    'guard' => '',
    'session_var' => 'google2fa',
    'otp_input' => 'one_time_password',
    'window' => 1,
    'forbid_old_passwords' => false,
    'otp_secret_column' => 'google2fa_secret',
    'view' => 'auth.2fa_verify',
    'session_variable' => 'google2fa_passed',
    'error_messages' => [
        'wrong_otp' => "The 'One Time Password' typed was wrong.",
        'cannot_be_empty' => 'One Time Password cannot be empty.',
        'unknown' => 'An unknown error has occurred. Please try again.',
    ],
    'throw_exceptions' => env('OTP_THROW_EXCEPTION', true),
    'qrcode_image_backend' => \PragmaRX\Google2FALaravel\Support\Constants::QRCODE_IMAGE_BACKEND_SVG,
];

Step 10: Add Routes

Update routes/web.php:

<?php

use App\Http\Controllers\TwoFactorController;
use Illuminate\Support\Facades\Route;

Auth::routes();

Route::get('/',function(){
    return view('welcome');
});

Route::middleware(['auth'])->group(function () {
    Route::get('/2fa/setup', [TwoFactorController::class, 'setup'])->name('2fa.setup');
    Route::get('/2fa/disable', [TwoFactorController::class, 'showDisableForm'])->name('2fa.disable.form');
    Route::post('/2fa/enable', [TwoFactorController::class, 'enable'])->name('2fa.enable');
    Route::post('/2fa/disable', [TwoFactorController::class, 'disable'])->name('2fa.disable');
});

Route::middleware(['auth', '2fa'])->group(function () {
    Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
    Route::post('/2fa', function () {
        return redirect()->intended('/home');
    })->name('2fa');
});

Step 11: Create TwoFactorController

Create app/Http/Controllers/TwoFactorController.php:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;

class TwoFactorController extends Controller
{
    public function setup()
    {
        $google2fa = app('pragmarx.google2fa');

        $user = Auth::user();

        if ($user->google2fa_secret) {
            return redirect()->route('home')->with('info', '2FA already enabled.');
        }

        $secret = $google2fa->generateSecretKey();

        $qrUrl = $google2fa->getQRCodeUrl(
            config('app.name'),
            $user->email,
            $secret
        );

        $writer = new Writer(
            new ImageRenderer(
                new RendererStyle(200),
                new SvgImageBackEnd()
            )
        );

        $qrCodeSvg = base64_encode($writer->writeString($qrUrl));

        session(['2fa_secret' => $secret]);

        return view('auth.2fa_setup', compact('qrCodeSvg', 'secret'));
    }

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

        $user = Auth::user();
        $secret = session('2fa_secret');
        $google2fa = app('pragmarx.google2fa');

        if ($google2fa->verifyKey($secret, $request->otp)) {
            $user->google2fa_secret = $secret;
            $user->save();

            return redirect()->route('home')->with('success', '2FA enabled!');
        }

        return back()->with('error', 'Invalid OTP, please try again.');
    }

    public function showDisableForm()
    {
        $user = Auth::user();

        if (!$user->google2fa_secret) {
            return redirect()->route('home')->with('info', '2FA is not enabled.');
        }

        return view('auth.2fa_disable');
    }

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

        $user = Auth::user();

        if (!$user->google2fa_secret) {
            return redirect()->route('home')->with('info', '2FA is not enabled.');
        }

        $google2fa = app('pragmarx.google2fa');

        $otpValid = $google2fa->verifyKey($user->google2fa_secret, $request->input('otp'));

        if (!$otpValid) {
            return back()->with('error', 'Invalid OTP. Please try again.');
        }

        $user->google2fa_secret = null;
        $user->save();

        session()->forget('google2fa_passed');

        return redirect()->route('home')->with('success', '2FA has been disabled.');
    }
}

Step 12: Create 2FA Setup View

Create resources/views/auth/2fa_setup.blade.php:

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-6">
                <h2>Enable Two-Factor Authentication</h2>
                <p>Set up your two factor authentication by scanning the barcode below. Alternatively, you can use the code
                    <strong>{{ $secret }}</strong></p>
                <p>Ensure you submit the current one because it refreshes every 30 seconds.</p>
                <img src="data:image/svg+xml;base64,{{ $qrCodeSvg }}" alt="QR Code">

                <form method="POST" action="{{ route('2fa.enable') }}" class="mt-4">
                    @csrf
                    <div class="form-group">
                        <label for="otp">Enter OTP from app:</label>
                        <input type="number" name="otp" id="otp" class="form-control" required>
                    </div>
                    @error('otp')
                        <span class="invalid-feedback" role="alert">
                            <strong>{{ $message }}</strong>
                        </span>
                    @enderror

                    @if (session('error') && !$errors->has('otp'))
                        <span class="invalid-feedback" role="alert" style="display: block;">
                            <strong>{{ session('error') }}</strong>
                        </span>
                    @endif
                    <button class="btn btn-primary mt-2" type="submit">Enable 2FA</button>
                </form>
            </div>
        </div>
    </div>
@endsection

Step 13: Create 2FA Verification View

Create resources/views/auth/2fa_verify.blade.php:

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row py-5 justify-content-center">
            <div class="col-md-6">
                <div class="card p-4 bg-white">
                    <h2>Two-Factor Authentication</h2>

                <form method="POST" action="{{ route('2fa') }}">
                    @csrf
                    <div class="form-group">
                        <p>Please enter the  <strong>OTP</strong> generated on your Authenticator App. <br> Ensure you submit the current one because it refreshes every 30 seconds.</p>
                        <label for="one_time_password">Enter the 6-digit OTP from your app:</label>
                        <input type="number" name="one_time_password" class="form-control" required>
                    </div>
                    @if (count($errors) > 0)
                       @foreach ($errors->all() as $error)
                        <p class="text-danger">{{ $error }}</p>
                    @endforeach
                    @endif
                    <button class="btn btn-primary mt-3" type="submit">Verify</button>
                </form>
                </div>
            </div>
        </div>
    </div>
@endsection

Step 14: Create 2FA Disable View

Create resources/views/auth/2fa_disable.blade.php:

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row py-5 justify-content-center">
            <div class="col-md-6">
                <div class="card p-4 bg-white">
                    <h2>Disable Two-Factor Authentication</h2>

                    <form method="POST" action="{{ route('2fa.disable') }}">
                        @csrf
                        <div class="form-group">
                            <p>Please enter the <strong>OTP</strong> generated on your Authenticator App. <br> Ensure you
                                submit the current one because it refreshes every 30 seconds.</p>
                            <label for="one_time_password">Enter OTP to disable 2FA:</label>
                            <input type="number" name="otp" class="form-control" required>
                        </div>
                        @if (count($errors) > 0)
                            @foreach ($errors->all() as $error)
                                <p class="text-danger">{{ $error }}</p>
                            @endforeach
                        @endif
                        <button class="btn btn-danger mt-3" type="submit">Disable 2FA</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
@endsection

Step 15: Register Middleware

Update bootstrap/app.php to register the 2FA middleware:

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->alias([
            '2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

Step 16: Add 2FA Links to Navigation

Update your navigation menu (e.g., resources/views/layouts/app.blade.php) to include 2FA links:

@auth
    @if (Auth::user()->google2fa_secret)
        <li class="nav-item">
            <a class="nav-link" href="{{ route('2fa.disable.form') }}">Disable 2FA</a>
        </li>
    @else
        <li class="nav-item">
            <a class="nav-link" href="{{ route('2fa.setup') }}">Enable 2FA </a>
        </li>
    @endif
@endauth

Testing the Implementation

  1. Run the development server:

    php artisan serve
  2. Register a user and navigate to /2fa/setup.

  3. Scan the QR code using Google Authenticator or a similar app.

  4. Enter the OTP to enable 2FA.

  5. Now, whenever the user logs in, they'll be prompted for a 2FA code.

Conclusion

You've successfully implemented Google Authenticator 2FA in Laravel! This enhances your application's security by requiring users to verify their identity with a time-based OTP.

Tags
Share :


+91 8447 525 204 Request Estimate