Kritim Yantra
Jul 03, 2025
Imagine this:
You launch a Laravel app. It’s beautiful. Fast. Functional. But there’s one thing missing... security.
One day, a user sends you a message:
“Hey! Someone logged into my account. Can you add 2FA?”
That’s your cue. Two-Factor Authentication (2FA) isn’t just a nice-to-have—it’s essential for protecting user accounts. If you’ve ever used a code from your phone to log in, you've used 2FA.
In this blog, you’ll learn how to implement 2FA in Laravel 12 with a friendly, step-by-step tutorial — no experience needed.
2FA adds a second layer of security to your app login.
Instead of just using a password (which can be stolen), users need a second “factor” — usually a time-based code from an app like Google Authenticator or Authy.
🛡️ With 2FA:
pragmarx/google2fa-laravel
package for generating codesStart fresh (or use an existing app):
composer create-project laravel/laravel laravel-2fa
cd laravel-2fa
php artisan serve
Create auth scaffolding:
composer require laravel/breeze --dev
php artisan breeze:install
npm install && npm run dev
php artisan migrate
Now you’ve got login, registration, and dashboard out of the box.
We’ll use PragmaRX's Google2FA package.
composer require pragmarx/google2fa-laravel
Now publish the config:
php artisan vendor:publish --provider="PragmaRX\Google2FALaravel\ServiceProvider"
This will create a config/google2fa.php
file where you can customize settings if needed.
Add a new column to store the 2FA secret key.
php artisan make:migration add_google2fa_secret_to_users_table
Schema::table('users', function (Blueprint $table) {
$table->text('google2fa_secret')->nullable();
});
Then run the migration:
php artisan migrate
User.php
, make the field fillable:protected $fillable = [
'name', 'email', 'password', 'google2fa_secret',
];
Let’s create a setup screen so users can enable 2FA using their authenticator app.
use App\Http\Controllers\TwoFactorController;
Route::middleware(['auth'])->group(function () {
Route::get('/2fa', [TwoFactorController::class, 'show'])->name('2fa.show');
Route::post('/2fa/setup', [TwoFactorController::class, 'setup'])->name('2fa.setup');
});
php artisan make:controller TwoFactorController
TwoFactorController.php
:use Illuminate\Http\Request;
use PragmaRX\Google2FALaravel\Support\Authenticator;
use Google2FA;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Writer;
public function show(Request $request)
{
$user = $request->user();
// Generate secret if it doesn't exist
if (!$user->google2fa_secret) {
$user->google2fa_secret = Google2FA::generateSecretKey();
$user->save();
}
// Create QR Code URL
$QR_Image = $this->generateQRCode($user->email, $user->google2fa_secret);
return view('2fa.setup', [
'QR_Image' => $QR_Image,
'secret' => $user->google2fa_secret
]);
}
public function setup(Request $request)
{
$request->validate([
'otp' => 'required|digits:6',
]);
$google2fa = app('pragmarx.google2fa');
if ($google2fa->verifyKey(auth()->user()->google2fa_secret, $request->otp)) {
return redirect('/dashboard')->with('success', '2FA enabled successfully.');
}
return back()->withErrors(['otp' => 'Invalid verification code.']);
}
private function generateQRCode($email, $secret)
{
$QRContent = Google2FA::getQRCodeUrl(
config('app.name'),
$email,
$secret
);
$renderer = new ImageRenderer(
new RendererStyle(200),
new SvgImageBackEnd()
);
$writer = new Writer($renderer);
return 'data:image/svg+xml;base64,' . base64_encode($writer->writeString($QRContent));
}
Create a file at: resources/views/2fa/setup.blade.php
<x-app-layout>
<h2>🔐 Two-Factor Authentication Setup</h2>
<p>Scan this QR code with Google Authenticator or Authy:</p>
<div>
<img src="{{ $QR_Image }}" />
</div>
<form method="POST" action="{{ route('2fa.setup') }}">
@csrf
<label>Enter the 6-digit code:</label>
<input type="text" name="otp" required>
<button type="submit">Verify</button>
</form>
</x-app-layout>
We’ll now ensure users enter a valid 2FA code after logging in.
php artisan make:middleware Ensure2FAIsVerified
In Ensure2FAIsVerified.php
:
public function handle($request, Closure $next)
{
if ($request->user()->google2fa_secret && !$request->session()->get('2fa_verified')) {
return redirect('/2fa/verify');
}
return $next($request);
}
bootstrap/app.php
Open your bootstrap/app.php
and update the withMiddleware()
call like this:
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
)
->withMiddleware(function (Middleware $middleware) {
// Register your 2FA middleware alias
$middleware->alias([
'2fa' => \App\Http\Middleware\Ensure2FAIsVerified::class,
]);
// Optionally, include it in the global or group stacks:
// $middleware->prepend(\App\Http\Middleware\Ensure2FAIsVerified::class);
// $middleware->appendToGroup('web', \App\Http\Middleware\Ensure2FAIsVerified::class);
})
->create();
Then protect routes like this:
Route::middleware(['auth', '2fa'])->get('/dashboard', function () {
return view('dashboard');
});
Add a route and controller method:
Route::get('/2fa/verify', [TwoFactorController::class, 'verifyForm']);
Route::post('/2fa/verify', [TwoFactorController::class, 'verifyCode']);
In the controller:
public function verifyForm()
{
return view('2fa.verify');
}
public function verifyCode(Request $request)
{
$request->validate(['otp' => 'required|digits:6']);
if (Google2FA::verifyKey(auth()->user()->google2fa_secret, $request->otp)) {
$request->session()->put('2fa_verified', true);
return redirect('/dashboard');
}
return back()->withErrors(['otp' => 'Invalid code.']);
}
Then create resources/views/2fa/verify.blade.php
:
<x-app-layout>
<h2>🔑 Verify 2FA</h2>
<form method="POST" action="{{ url('/2fa/verify') }}">
@csrf
<label>Enter your 6-digit code:</label>
<input type="text" name="otp" required>
<button type="submit">Verify</button>
</form>
</x-app-layout>
You could add a route/button to let users remove 2FA:
public function disable(Request $request)
{
$request->user()->update(['google2fa_secret' => null]);
$request->session()->forget('2fa_verified');
return redirect('/dashboard')->with('success', '2FA disabled.');
}
Let’s recap:
Your app now has enterprise-grade security 🎉
Happy Coding, and Stay Secure! 🔐
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google