scheb/2fa-bundle
Symfony bundle providing a generic framework for adding two-factor authentication (2FA) to your app. Integrates with Symfony Security and supports multiple 2FA methods via a consistent interface, with full docs on symfony.com.
Installation:
scheb/2fa and adapting it.composer require spomky-labs/otphp # For TOTP/HOTP logic (used by scheb/2fa)
composer require endroid/qr-code # For QR code generation
Database Setup:
php artisan make:migration create_two_factor_secrets_table
Schema::create('two_factor_secrets', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('secret')->unique();
$table->string('algorithm')->default('sha1');
$table->string('digits')->default('6');
$table->string('period')->nullable();
$table->timestamps();
});
Basic Middleware:
php artisan make:middleware CheckTwoFactor
spomky-labs/otphp):
public function handle($request, Closure $next)
{
if (auth()->check() && auth()->user()->mustVerifyTwoFactor()) {
$totp = new TOTP($request->input('two_factor_secret'));
if (!$totp->verify($request->input('two_factor_code'))) {
return redirect()->back()->withErrors(['2fa' => 'Invalid code.']);
}
}
return $next($request);
}
First Use Case:
/dashboard) with the middleware:
Route::middleware(['auth', 'check.two.factor'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
Route::get('/enable-2fa', [TwoFactorController::class, 'showSetupForm']);
Route::post('/enable-2fa', [TwoFactorController::class, 'storeSetup']);
User Setup Flow:
public function showSetupForm()
{
$secret = $this->generateSecret();
$qrCodeUrl = $this->generateQrCodeUrl($secret);
return view('two-factor.setup', compact('secret', 'qrCodeUrl'));
}
public function storeSetup(Request $request)
{
$user = auth()->user();
$user->twoFactorSecret()->create([
'secret' => $request->input('secret'),
'algorithm' => 'sha1',
'digits' => '6',
]);
return redirect()->route('verify.2fa');
}
Verify Setup:
Route::get('/verify-2fa', [TwoFactorController::class, 'showVerificationForm']);
Route::post('/verify-2fa', [TwoFactorController::class, 'verifySetup']);
Modular Middleware:
CheckTwoFactor: Validates TOTP on protected routes.VerifyTwoFactorSetup: Ensures 2FA is enabled before allowing access.Route::middleware(['auth', 'verified', 'check.two.factor'])->group(...);
Service-Based Logic:
TwoFactorService):
class TwoFactorService {
public function generateSecret(): string
{
return bin2hex(random_bytes(20));
}
public function generateQrCodeUrl(string $secret, string $issuer = 'YourApp'): string
{
return 'otpauth://totp/' . urlencode($issuer) . ':' . urlencode(auth()->user()->email) . '?secret=' . urlencode($secret) . '&issuer=' . urlencode($issuer);
}
public function verifyCode(string $secret, string $code, int $digits = 6): bool
{
$totp = new TOTP($secret);
return $totp->verify($code, $digits);
}
}
Event-Driven Workflows:
TwoFactorEnabled: Triggered when a user enables 2FA.TwoFactorVerificationFailed: Log failed attempts.class TwoFactorService {
public function enableTwoFactor(User $user, string $secret)
{
$user->twoFactorSecret()->create([
'secret' => $secret,
'algorithm' => 'sha1',
]);
event(new TwoFactorEnabled($user));
}
}
Backup Codes:
public function generateBackupCodes(int $count = 10): array
{
$codes = [];
for ($i = 0; $i < $count; $i++) {
$codes[] = bin2hex(random_bytes(3));
}
return $codes;
}
$user->twoFactorBackupCodes()->createMany(
array_map(fn ($code) => ['code' => hash('sha256', $code)], $backupCodes)
);
Recovery Flow:
Route::post('/recover-2fa', [TwoFactorController::class, 'recover'])->name('recover.2fa');
User Enrollment:
/enable-2fa).Login Flow:
Recovery Flow:
/recover-2fa).Laravel Auth Integration:
User model to track 2FA status:
class User extends Authenticatable {
public function mustVerifyTwoFactor(): bool
{
return $this->twoFactorSecret()->exists();
}
}
Session Binding:
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->isRedirect() && auth()->check() && auth()->user()->mustVerifyTwoFactor()) {
$response->headers->set('X-TwoFactor-Bound', 'true');
}
return $response;
}
Rate Limiting:
Route::middleware(['throttle:5,1'])->post('/verify-2fa', ...);
Testing:
spomky-labs/otphp to mock TOTP verification in tests:
$totp = Mockery::mock(TOTP::class);
$totp->shouldReceive('verify')->andReturn(true);
$this->app->instance(TOTP::class, $totp);
Artisan Commands:
php artisan make:command GenerateBackupCodes
class GenerateBackupCodes extends Command {
protected $signature = 'two-factor:backup-codes {user?}';
public function handle()
{
How can I help you explore Laravel packages today?