2FAA.app

Laravel 2FA Tutorial

Add TOTP two-factor authentication to a Laravel app using pragmarx/google2fa-laravel. This guide covers secret generation, QR code display, code verification, and how to test the flow against 2FAA's authenticator.

1. Install the package

composer require pragmarx/google2fa-laravel
composer require bacon/bacon-qr-code

google2fa-laravel handles TOTP generation/verification. bacon-qr-code renders the QR code so users can scan with an authenticator.

2. Add a 2FA secret column

// database/migrations/2026_xx_xx_add_2fa_secret_to_users.php
Schema::table('users', function (Blueprint $table) {
    $table->string('two_factor_secret')->nullable();
    $table->boolean('two_factor_enabled')->default(false);
});

3. Generate and show the secret

use PragmaRX\Google2FA\Google2FA;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;

public function setup(Request $request)
{
    $g2fa = new Google2FA();
    $secret = $g2fa->generateSecretKey();

    $request->user()->update(['two_factor_secret' => $secret]);

    $otpauthUri = $g2fa->getQRCodeUrl(
        config('app.name'),
        $request->user()->email,
        $secret
    );

    $writer = new Writer(new ImageRenderer(
        new RendererStyle(192),
        new SvgImageBackEnd()
    ));
    $qrSvg = $writer->writeString($otpauthUri);

    return view('two-factor.setup', compact('secret', 'qrSvg'));
}

4. Verify the code on confirm

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

    $g2fa = new Google2FA();
    $valid = $g2fa->verifyKey(
        $request->user()->two_factor_secret,
        $request->code,
        2 // accept ±1 window for clock drift
    );

    if (! $valid) {
        return back()->withErrors(['code' => 'Invalid 2FA code']);
    }

    $request->user()->update(['two_factor_enabled' => true]);

    return redirect()->route('dashboard')
        ->with('status', '2FA enabled');
}

5. Require 2FA at login

// app/Http/Middleware/Require2FA.php
public function handle($request, Closure $next)
{
    $user = $request->user();
    if ($user && $user->two_factor_enabled && ! session('2fa_passed')) {
        return redirect()->route('two-factor.challenge');
    }
    return $next($request);
}

On the challenge route, accept a 6-digit code and call verifyKey() the same way as in step 4. On success, set session(['2fa_passed' => true]) and redirect.

Test the flow with 2FAA

Two ways to test your Laravel 2FA implementation:

  1. Web: open 2FAA's authenticator, paste the generated Base32 secret, and use the 6-digit code to confirm setup.
  2. From your AI agent: install the 2faa-mcp server and have Claude Code generate test codes on demand — useful when writing end-to-end tests.

Recovery codes (recommended)

Generate 8–10 single-use recovery codes when 2FA is enabled. Store them hashed (just like passwords). Let the user enter one if they lose their authenticator.

$codes = collect(range(1, 10))
    ->map(fn() => Str::random(10))
    ->all();

$user->update([
    'two_factor_recovery_codes' => encrypt(json_encode($codes)),
]);

// show $codes to the user once

Related