Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Filament Two Factor Authentication (2FA)
https://github.com/stephenjude/filament-two-factor-authentication
Admin
Adds two-factor authentication, including Google 2FA and Passkey authentication, to new and existing
...
Tokens:
8,243
Snippets:
54
Trust Score:
9.3
Update:
2 months ago
Context
Skills
Chat
Benchmark
89.8
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Filament Two Factor Authentication (2FA) Filament Two Factor Authentication is a Laravel package that adds Google 2FA (TOTP) and Passkey authentication to Filament admin panels. It provides a complete two-factor authentication solution including QR code setup, recovery codes, login challenges, and optional enforcement of 2FA for all users. The package integrates seamlessly with Filament's panel system and supports multiple authentication methods. The package leverages `pragmarx/google2fa` for TOTP generation and verification, `bacon/bacon-qr-code` for QR code rendering, and `spatie/laravel-passkeys` for WebAuthn passkey support. It includes Livewire components for user-facing 2FA management, middleware for challenge enforcement, and a comprehensive event system for tracking authentication lifecycle events. ## Installation Install the package via Composer and run the installation command. ```bash # Install the package composer require stephenjude/filament-two-factor-authentication # Run the installation command to publish migrations php artisan filament-two-factor-authentication:install # Run migrations to add 2FA columns to users table php artisan migrate # Optionally publish views for customization php artisan vendor:publish --tag="filament-two-factor-authentication-views" ``` ## User Model Setup Add the `TwoFactorAuthenticatable` trait and `HasPasskeys` interface to your User model. ```php <?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Filament\Models\Contracts\FilamentUser; use Spatie\LaravelPasskeys\Models\Concerns\HasPasskeys; use Stephenjude\FilamentTwoFactorAuthentication\TwoFactorAuthenticatable; class User extends Authenticatable implements FilamentUser, HasPasskeys { use TwoFactorAuthenticatable; protected $fillable = [ 'name', 'email', 'password', ]; protected $hidden = [ 'password', 'remember_token', 'two_factor_secret', 'two_factor_recovery_codes', ]; protected $casts = [ 'email_verified_at' => 'datetime', 'two_factor_confirmed_at' => 'datetime', ]; public function canAccessPanel(\Filament\Panel $panel): bool { return true; } } ``` ## TwoFactorAuthenticationPlugin::make() Creates and configures the Two Factor Authentication plugin instance for a Filament panel. This is the main entry point for enabling 2FA features in your application. ```php <?php use Filament\Panel; use Stephenjude\FilamentTwoFactorAuthentication\TwoFactorAuthenticationPlugin; class AdminPanelProvider extends PanelProvider { public function panel(Panel $panel): Panel { return $panel ->default() ->id('admin') ->path('admin') ->login() ->plugins([ TwoFactorAuthenticationPlugin::make() ->enableTwoFactorAuthentication() // Enable Google 2FA (TOTP) ->enablePasskeyAuthentication() // Enable Passkey/WebAuthn ->addTwoFactorMenuItem() // Add 2FA settings to user menu ->forceTwoFactorSetup(), // Force all users to setup 2FA ]); } } ``` ## enableTwoFactorAuthentication() Enables Google Authenticator (TOTP) based two-factor authentication with configurable challenge middleware. ```php <?php use Stephenjude\FilamentTwoFactorAuthentication\TwoFactorAuthenticationPlugin; use Stephenjude\FilamentTwoFactorAuthentication\Middleware\TwoFactorChallenge; TwoFactorAuthenticationPlugin::make() ->enableTwoFactorAuthentication( condition: true, // Enable/disable conditionally challengeMiddleware: TwoFactorChallenge::class // Custom middleware for 2FA challenge ); // Conditional enabling based on environment TwoFactorAuthenticationPlugin::make() ->enableTwoFactorAuthentication( condition: fn () => app()->environment('production'), ); ``` ## enablePasskeyAuthentication() Enables WebAuthn passkey authentication allowing users to log in using biometrics or hardware security keys. ```php <?php use Stephenjude\FilamentTwoFactorAuthentication\TwoFactorAuthenticationPlugin; TwoFactorAuthenticationPlugin::make() ->enablePasskeyAuthentication(condition: true); // Enable passkey only for specific conditions TwoFactorAuthenticationPlugin::make() ->enablePasskeyAuthentication( condition: fn () => config('app.enable_passkeys', true) ); ``` ## forceTwoFactorSetup() Forces all users to set up two-factor authentication before accessing the panel. Users without 2FA enabled will be redirected to the setup page. ```php <?php use Stephenjude\FilamentTwoFactorAuthentication\TwoFactorAuthenticationPlugin; use Stephenjude\FilamentTwoFactorAuthentication\Middleware\ForceTwoFactorSetup; TwoFactorAuthenticationPlugin::make() ->enableTwoFactorAuthentication() ->forceTwoFactorSetup( condition: true, // Enable enforcement requiresPassword: true, // Require password confirmation middleware: ForceTwoFactorSetup::class // Custom enforcement middleware ); // Force 2FA setup only for admin users TwoFactorAuthenticationPlugin::make() ->enableTwoFactorAuthentication() ->forceTwoFactorSetup( condition: fn () => auth()->user()?->is_admin ?? false, requiresPassword: false, ); ``` ## addTwoFactorMenuItem() Adds a menu item to the user menu for accessing 2FA settings. ```php <?php use Stephenjude\FilamentTwoFactorAuthentication\TwoFactorAuthenticationPlugin; TwoFactorAuthenticationPlugin::make() ->enableTwoFactorAuthentication() ->addTwoFactorMenuItem( condition: true, // Show/hide the menu item label: 'Security Settings', // Custom menu label icon: 'heroicon-o-shield-check' // Custom Heroicon ); // Default configuration TwoFactorAuthenticationPlugin::make() ->enableTwoFactorAuthentication() ->addTwoFactorMenuItem(); // Uses default label "2FA" and lock icon ``` ## TwoFactorAuthenticatable Trait The trait provides methods for managing 2FA state on the User model. It handles secret storage, recovery codes, QR code generation, and authentication state. ```php <?php use App\Models\User; $user = User::find(1); // Check if user has 2FA enabled and confirmed $hasEnabled = $user->hasEnabledTwoFactorAuthentication(); // Returns: true/false // Check if user has any passkeys registered $hasPasskeys = $user->hasEnabledPasskeyAuthentication(); // Returns: true/false // Get recovery codes (decrypted) $codes = $user->recoveryCodes(); // Returns: ['abc123-def456', 'ghi789-jkl012', ...] // Replace a used recovery code with a new one $user->replaceRecoveryCode('abc123-def456'); // Get QR code SVG for authenticator app setup $svg = $user->twoFactorQrCodeSvg(); // Returns: '<svg>...</svg>' // Get QR code URL (otpauth:// format) $url = $user->twoFactorQrCodeUrl(); // Returns: 'otpauth://totp/AppName:user@email.com?secret=...' // Check if 2FA challenge has been passed in current session $passed = $user->isTwoFactorChallengePassed(); // Returns: true/false // Mark 2FA challenge as passed (used after successful verification) $user->setTwoFactorChallengePassed(); ``` ## EnableTwoFactorAuthentication Action Programmatically enables two-factor authentication for a user by generating a secret key and recovery codes. ```php <?php use App\Models\User; use Stephenjude\FilamentTwoFactorAuthentication\Actions\EnableTwoFactorAuthentication; $user = User::find(1); // Enable 2FA for the user (creates secret and recovery codes) app(EnableTwoFactorAuthentication::class)($user); // Force regenerate secret even if one exists app(EnableTwoFactorAuthentication::class)($user, force: true); // After enabling, user can scan QR code $qrCodeSvg = $user->twoFactorQrCodeSvg(); $secretKey = decrypt($user->two_factor_secret); ``` ## ConfirmTwoFactorAuthentication Action Confirms the 2FA setup by verifying a code from the user's authenticator app. This finalizes the 2FA enrollment. ```php <?php use App\Models\User; use Stephenjude\FilamentTwoFactorAuthentication\Actions\ConfirmTwoFactorAuthentication; use Illuminate\Validation\ValidationException; $user = User::find(1); $codeFromAuthenticatorApp = '123456'; try { // Verify and confirm the 2FA setup app(ConfirmTwoFactorAuthentication::class)($user, $codeFromAuthenticatorApp); // User's two_factor_confirmed_at is now set // 2FA is fully active for this user } catch (ValidationException $e) { // Invalid code provided $errors = $e->errors(); // ['data.code' => ['The provided two factor authentication code was invalid.']] } ``` ## DisableTwoFactorAuthentication Action Disables two-factor authentication for a user by clearing all 2FA-related data. ```php <?php use App\Models\User; use Stephenjude\FilamentTwoFactorAuthentication\Actions\DisableTwoFactorAuthentication; $user = User::find(1); // Disable 2FA for the user app(DisableTwoFactorAuthentication::class)($user); // This clears: // - two_factor_secret // - two_factor_recovery_codes // - two_factor_confirmed_at // Dispatches TwoFactorAuthenticationDisabled event ``` ## GenerateNewRecoveryCodes Action Generates a fresh set of 8 recovery codes for the user, replacing any existing codes. ```php <?php use App\Models\User; use Stephenjude\FilamentTwoFactorAuthentication\Actions\GenerateNewRecoveryCodes; $user = User::find(1); // Generate new recovery codes (replaces existing ones) app(GenerateNewRecoveryCodes::class)($user); // Get the new codes $newCodes = $user->recoveryCodes(); // Returns array of 8 recovery codes like: ['abc123-def456', ...] // Dispatches RecoveryCodesGenerated event ``` ## TwoFactorAuthenticationProvider The core provider for generating secrets, QR code URLs, and verifying TOTP codes. ```php <?php use Stephenjude\FilamentTwoFactorAuthentication\TwoFactorAuthenticationProvider; $provider = app(TwoFactorAuthenticationProvider::class); // Generate a new secret key $secret = $provider->generateSecretKey(); // Returns: 'JBSWY3DPEHPK3PXP' (Base32 encoded) // Generate QR code URL for authenticator apps $qrUrl = $provider->qrCodeUrl( companyName: 'My Application', companyEmail: 'user@example.com', secret: $secret ); // Returns: 'otpauth://totp/My%20Application:user@example.com?secret=JBSWY3DPEHPK3PXP' // Verify a TOTP code $isValid = $provider->verify( secret: $secret, code: '123456' ); // Returns: true/false ``` ## TwoFactorChallenge Middleware Middleware that intercepts authenticated requests and redirects users to the 2FA challenge page if they haven't passed verification. ```php <?php namespace App\Http\Middleware; use Stephenjude\FilamentTwoFactorAuthentication\Middleware\TwoFactorChallenge; // The middleware is automatically registered by the plugin // It checks if: // 1. User has 2FA enabled // 2. User hasn't passed the 2FA challenge // 3. User didn't authenticate via passkey // Custom middleware extending the base class CustomTwoFactorChallenge extends TwoFactorChallenge { public function handle($request, $next): mixed { // Skip 2FA for specific routes if ($request->is('api/*')) { return $next($request); } return parent::handle($request, $next); } protected function twoFactorChallengeRoute(): ?string { // Custom challenge route return route('custom.2fa.challenge'); } } ``` ## ForceTwoFactorSetup Middleware Middleware that forces users to set up 2FA before accessing any other page in the panel. ```php <?php namespace App\Http\Middleware; use Stephenjude\FilamentTwoFactorAuthentication\Middleware\ForceTwoFactorSetup; // The middleware is automatically registered when forceTwoFactorSetup() is called // It redirects users without 2FA to the setup page // Excluded routes (handled automatically): // - */logout // - logout // - */profile // Custom middleware extending the base class CustomForceTwoFactorSetup extends ForceTwoFactorSetup { public function handle($request, $next): mixed { // Allow API access without 2FA if ($request->is('api/*')) { return $next($request); } return parent::handle($request, $next); } protected function redirectTo(): ?string { // Custom setup page URL return route('custom.2fa.setup'); } } ``` ## Livewire Components Embed the 2FA management components in custom profile pages or settings views. ```php {{-- In a Blade view --}} <x-filament-panels::page> {{-- Two Factor Authentication Management --}} @livewire(\Stephenjude\FilamentTwoFactorAuthentication\Livewire\TwoFactorAuthentication::class) {{-- Passkey Management --}} @livewire(\Stephenjude\FilamentTwoFactorAuthentication\Livewire\PasskeyAuthentication::class) </x-filament-panels::page> {{-- With custom configuration --}} @livewire(\Stephenjude\FilamentTwoFactorAuthentication\Livewire\TwoFactorAuthentication::class, [ 'aside' => false, // Layout mode 'redirectTo' => '/dashboard' // Redirect after setup ]) ``` ## Event Listeners Subscribe to 2FA lifecycle events in your EventServiceProvider to implement custom logic like logging, notifications, or audit trails. ```php <?php namespace App\Providers; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Stephenjude\FilamentTwoFactorAuthentication\Events\TwoFactorAuthenticationEnabled; use Stephenjude\FilamentTwoFactorAuthentication\Events\TwoFactorAuthenticationDisabled; use Stephenjude\FilamentTwoFactorAuthentication\Events\TwoFactorAuthenticationChallenged; use Stephenjude\FilamentTwoFactorAuthentication\Events\TwoFactorAuthenticationConfirmed; use Stephenjude\FilamentTwoFactorAuthentication\Events\TwoFactorAuthenticationFailed; use Stephenjude\FilamentTwoFactorAuthentication\Events\ValidTwoFactorAuthenticationCodeProvided; use Stephenjude\FilamentTwoFactorAuthentication\Events\RecoveryCodesGenerated; use Stephenjude\FilamentTwoFactorAuthentication\Events\RecoveryCodeReplaced; use Stephenjude\FilamentTwoFactorAuthentication\Events\ValidTwoFactorRecoveryCodeProvided; class EventServiceProvider extends ServiceProvider { protected $listen = [ // User enabled 2FA (secret generated, not yet confirmed) TwoFactorAuthenticationEnabled::class => [ \App\Listeners\LogTwoFactorEnabled::class, ], // User confirmed 2FA with valid code (setup complete) TwoFactorAuthenticationConfirmed::class => [ \App\Listeners\SendTwoFactorConfirmationEmail::class, ], // User disabled 2FA TwoFactorAuthenticationDisabled::class => [ \App\Listeners\LogTwoFactorDisabled::class, \App\Listeners\NotifySecurityTeam::class, ], // User was presented with 2FA challenge during login TwoFactorAuthenticationChallenged::class => [ \App\Listeners\LogChallengeAttempt::class, ], // User provided incorrect 2FA code TwoFactorAuthenticationFailed::class => [ \App\Listeners\LogFailedAttempt::class, \App\Listeners\CheckForBruteForce::class, ], // User successfully passed 2FA challenge ValidTwoFactorAuthenticationCodeProvided::class => [ \App\Listeners\LogSuccessfulVerification::class, ], // User successfully used a recovery code ValidTwoFactorRecoveryCodeProvided::class => [ \App\Listeners\AlertRecoveryCodeUsed::class, ], // User generated new recovery codes RecoveryCodesGenerated::class => [ \App\Listeners\LogRecoveryCodesRegenerated::class, ], // A single recovery code was replaced after use RecoveryCodeReplaced::class => [ \App\Listeners\LogRecoveryCodeReplacement::class, ], ]; } ``` ## Event Listener Implementation Example Create custom listeners to handle 2FA events with business logic. ```php <?php namespace App\Listeners; use Stephenjude\FilamentTwoFactorAuthentication\Events\TwoFactorAuthenticationFailed; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; use App\Notifications\SuspiciousLoginAttempt; class CheckForBruteForce { public function handle(TwoFactorAuthenticationFailed $event): void { $user = $event->user; $cacheKey = "2fa_failed_attempts:{$user->id}"; // Track failed attempts $attempts = cache()->increment($cacheKey); cache()->put($cacheKey, $attempts, now()->addMinutes(15)); Log::warning('2FA verification failed', [ 'user_id' => $user->id, 'email' => $user->email, 'attempts' => $attempts, 'ip' => request()->ip(), ]); // Alert on multiple failed attempts if ($attempts >= 3) { Notification::send($user, new SuspiciousLoginAttempt([ 'attempts' => $attempts, 'ip_address' => request()->ip(), 'user_agent' => request()->userAgent(), ])); } } } ``` ## Database Migration The package migration adds the required columns to the users table. ```php <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::table('users', function (Blueprint $table) { // Encrypted TOTP secret key $table->text('two_factor_secret') ->after('password') ->nullable(); // Encrypted JSON array of recovery codes $table->text('two_factor_recovery_codes') ->after('two_factor_secret') ->nullable(); // Timestamp when 2FA was confirmed (setup completed) $table->timestamp('two_factor_confirmed_at') ->after('two_factor_recovery_codes') ->nullable(); }); } public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn([ 'two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at', ]); }); } }; ``` ## Complete Panel Configuration Full example showing all available plugin options with advanced configurations. ```php <?php namespace App\Providers\Filament; use Filament\Panel; use Filament\PanelProvider; use Stephenjude\FilamentTwoFactorAuthentication\TwoFactorAuthenticationPlugin; use Stephenjude\FilamentTwoFactorAuthentication\Middleware\TwoFactorChallenge; use Stephenjude\FilamentTwoFactorAuthentication\Middleware\ForceTwoFactorSetup; class AdminPanelProvider extends PanelProvider { public function panel(Panel $panel): Panel { return $panel ->default() ->id('admin') ->path('admin') ->login() ->colors([ 'primary' => '#3b82f6', ]) ->plugins([ TwoFactorAuthenticationPlugin::make() // Enable Google Authenticator 2FA ->enableTwoFactorAuthentication( condition: true, challengeMiddleware: TwoFactorChallenge::class, ) // Enable Passkey/WebAuthn authentication ->enablePasskeyAuthentication( condition: true, ) // Force all users to setup 2FA ->forceTwoFactorSetup( condition: fn () => app()->environment('production'), requiresPassword: true, middleware: ForceTwoFactorSetup::class, ) // Add 2FA settings to user menu ->addTwoFactorMenuItem( condition: true, label: 'Two-Factor Auth', icon: 'heroicon-o-finger-print', ), ]); } } ``` ## Summary Filament Two Factor Authentication provides a complete, production-ready two-factor authentication solution for Laravel Filament applications. The primary use cases include securing admin panels with TOTP-based authentication, implementing passwordless login via WebAuthn passkeys, enforcing 2FA policies for compliance requirements, and providing users with self-service 2FA management. The package handles the entire authentication lifecycle including setup, verification, recovery codes, and account security events. Integration follows a simple pattern: install the package, add the trait to your User model, configure the plugin on your Filament panel, and optionally customize the behavior through middleware, events, and Livewire components. The event system enables building audit trails, security alerts, and custom business logic around authentication events. The package supports both optional 2FA (user choice) and mandatory 2FA (organization policy) through its flexible middleware configuration.