mortogo321 / laravel-2fa
A comprehensive Laravel package for Two-Factor Authentication with support for TOTP, WebAuthn/Passkey, SMS, and Email providers
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/mortogo321/laravel-2fa
Requires
- php: ^8.1
- bacon/bacon-qr-code: ^2.0
- laravel/framework: ^10.0
- pragmarx/google2fa: ^8.0
- web-auth/cose-lib: ^4.2
- web-auth/webauthn-lib: ^4.7
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0
- phpunit/phpunit: ^10.0
README
A comprehensive Laravel package for Two-Factor Authentication with support for multiple providers including TOTP (Google Authenticator, Authy), WebAuthn/Passkey, SMS, and Email.
Table of Contents
- Features
- Requirements
- Installation
- Configuration
- Quick Start
- Usage
- Provider-Specific Implementation
- Recovery Codes
- API Routes
- Middleware
- Customization
- Events
- Security Best Practices
- Testing
- Troubleshooting
- FAQ
- Contributing
- Changelog
- License
- Credits
- Support
Features
-
Multiple 2FA Providers:
- TOTP (Time-based One-Time Password): Works with Google Authenticator, Authy, and other TOTP apps
- WebAuthn/Passkey: Support for hardware security keys and platform authenticators (Touch ID, Face ID, Windows Hello)
- SMS: Send verification codes via SMS
- Email: Send verification codes via email
-
Recovery Codes: Generate and manage backup recovery codes
-
Flexible Configuration: Enable/disable providers per user
-
Middleware Protection: Easy route protection with middleware
-
Rate Limiting: Built-in brute-force protection
-
Session Management: Configurable 2FA verification persistence
-
Beautiful UI: Ready-to-use Blade templates with modern design
-
Extensible Architecture: Easy to add custom providers
Requirements
- PHP 8.1 or higher
- Laravel 10.0 or higher
Installation
1. Install via Composer
composer require mortogo321/laravel-2fa
2. Publish Configuration
php artisan vendor:publish --tag=two-factor-config
This will create a config/two-factor.php configuration file.
3. Publish Migrations
php artisan vendor:publish --tag=two-factor-migrations php artisan migrate
4. (Optional) Publish Views
If you want to customize the views:
php artisan vendor:publish --tag=two-factor-views
Configuration
Open config/two-factor.php and configure the providers you want to use:
return [ 'providers' => [ 'totp' => [ 'enabled' => true, 'issuer' => env('APP_NAME', 'Laravel'), 'qr_code_size' => 200, 'window' => 1, ], 'webauthn' => [ 'enabled' => true, 'timeout' => 60000, 'user_verification' => 'preferred', 'attestation' => 'none', ], 'sms' => [ 'enabled' => false, 'driver' => env('TWO_FACTOR_SMS_DRIVER', 'twilio'), 'from' => env('TWO_FACTOR_SMS_FROM'), 'code_length' => 6, 'code_expiry' => 5, ], 'email' => [ 'enabled' => true, 'from' => [ 'address' => env('MAIL_FROM_ADDRESS'), 'name' => env('MAIL_FROM_NAME'), ], 'code_length' => 6, 'code_expiry' => 5, ], ], 'recovery' => [ 'enabled' => true, 'count' => 8, 'length' => 10, ], 'user_model' => \App\Models\User::class, ];
Quick Start
Get started in 3 simple steps:
# 1. Install package composer require mortogo321/laravel-2fa # 2. Publish and run migrations php artisan vendor:publish --tag=two-factor-migrations php artisan migrate # 3. Add trait to User model
// app/Models/User.php use Mortogo321\Laravel2FA\Traits\HasTwoFactorAuthentication; class User extends Authenticatable { use HasTwoFactorAuthentication; }
// routes/web.php Route::middleware(['auth', 'two-factor'])->group(function () { Route::get('/dashboard', [DashboardController::class, 'index']); });
That's it! Your routes are now protected with 2FA. Users can enable it at /two-factor/settings.
Usage
1. Add Trait to User Model
Add the HasTwoFactorAuthentication trait to your User model:
use Mortogo321\Laravel2FA\Traits\HasTwoFactorAuthentication; class User extends Authenticatable { use HasTwoFactorAuthentication; // ... rest of your model }
2. Protect Routes with Middleware
Apply the two-factor middleware to routes that require 2FA verification:
// In routes/web.php Route::middleware(['auth', 'two-factor'])->group(function () { Route::get('/dashboard', [DashboardController::class, 'index']); Route::get('/profile', [ProfileController::class, 'show']); });
3. Add 2FA Settings to Your App
Create a link to the 2FA settings page:
<a href="{{ route('two-factor.index') }}">Two-Factor Authentication</a>
Using the Facade
The package provides a convenient facade for programmatic access:
use Mortogo321\Laravel2FA\Facades\TwoFactor; // Check if user has 2FA enabled if (TwoFactor::isEnabled($user, 'totp')) { // TOTP is enabled } // Generate credentials for TOTP $credentials = TwoFactor::generateCredentials($user, 'totp'); // Returns: ['secret' => '...', 'qr_code' => '...', 'qr_code_url' => '...'] // Verify a code $isValid = TwoFactor::verify($user, 'totp', '123456'); // Enable a provider TwoFactor::enable($user, 'totp', ['secret' => $secret]); // Disable a provider TwoFactor::disable($user, 'totp'); // Get all providers for a user $providers = TwoFactor::getUserProviders($user); // Mark user as verified in session TwoFactor::markAsVerified($user); // Check if user is verified in current session if (TwoFactor::isVerified($user)) { // User has completed 2FA challenge }
Provider-Specific Implementation
TOTP (Google Authenticator)
- User enables TOTP from settings page
- QR code is displayed
- User scans with authenticator app
- User enters code to verify
- TOTP is enabled
Programmatic Example:
// Generate secret and QR code $credentials = TwoFactor::generateCredentials($user, 'totp'); // Display QR code to user echo $credentials['qr_code']; // SVG QR code // After user scans and enters code, verify and enable $code = $request->input('code'); if (TwoFactor::verify($user, 'totp', $code)) { TwoFactor::enable($user, 'totp', ['secret' => $credentials['secret']]); }
WebAuthn/Passkey
WebAuthn requires JavaScript for registration and authentication. Example implementation:
// Registration const response = await fetch('/two-factor/enable/webauthn', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const { credentials } = await response.json(); const options = JSON.parse(credentials.options); // Use WebAuthn API const credential = await navigator.credentials.create({ publicKey: options }); // Send credential back to server for verification
SMS Provider
To use SMS, you need to implement the SMS sending logic in SmsProvider::sendSms(). Example with a notification:
// Create a notification php artisan make:notification TwoFactorCodeNotification // In the notification public function via($notifiable) { return ['vonage']; // or 'twilio', etc. } public function toVonage($notifiable) { return (new VonageMessage) ->content("Your 2FA code is: {$this->code}"); }
Then update SmsProvider.php:
protected function sendSms(Model $user, string $code): void { $user->notify(new TwoFactorCodeNotification($code)); }
Email Provider
Email codes are sent automatically using the included mailable. Make sure your mail configuration is set up in .env:
MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=your_username MAIL_PASSWORD=your_password MAIL_FROM_ADDRESS="noreply@example.com" MAIL_FROM_NAME="${APP_NAME}"
Recovery Codes
Recovery codes are automatically generated when a user enables their first 2FA method. Users can:
- View their recovery codes at any time
- Regenerate codes (invalidates old ones)
- Use a recovery code in place of a 2FA code
Using Recovery Codes Programmatically:
use Mortogo321\Laravel2FA\Services\RecoveryCodeService; $recoveryService = app(RecoveryCodeService::class); // Generate recovery codes $codes = $recoveryService->generate($user); // Returns array of plain-text codes (only time they're visible) // Verify a recovery code if ($recoveryService->verify($user, $code)) { // Code is valid and will be marked as used } // Check if user has unused codes if ($recoveryService->hasUnusedCodes($user)) { $count = $recoveryService->getRemainingCount($user); }
API Routes
The package provides these routes (prefixed with /two-factor by default):
| Method | URI | Name | Description |
|---|---|---|---|
| GET | /two-factor/challenge |
two-factor.challenge | Show 2FA challenge page |
| POST | /two-factor/challenge |
two-factor.verify | Verify 2FA code |
| GET | /two-factor/settings |
two-factor.index | Show 2FA settings |
| POST | /two-factor/enable/{provider} |
two-factor.enable | Enable a provider |
| POST | /two-factor/disable/{provider} |
two-factor.disable | Disable a provider |
| GET | /two-factor/recovery-codes |
two-factor.recovery-codes.show | View recovery codes |
| POST | /two-factor/recovery-codes |
two-factor.recovery-codes.regenerate | Regenerate codes |
Middleware
two-factor
Ensures users with 2FA enabled complete the challenge:
Route::middleware(['auth', 'two-factor'])->group(function () { // Protected routes });
two-factor.verified
Ensures user is in the challenge flow:
Route::middleware(['auth', 'two-factor.verified'])->group(function () { Route::get('/two-factor/challenge', ...); });
Customization
Custom Providers
Create a custom provider by implementing the TwoFactorProvider interface:
use Mortogo321\Laravel2FA\Contracts\TwoFactorProvider; class CustomProvider implements TwoFactorProvider { public function getName(): string { return 'custom'; } public function generateCredentials(Model $user): array { // Generate credentials } public function verify(Model $user, string $code, array $metadata = []): bool { // Verify code } // ... implement other methods }
Register your provider in a service provider:
use Mortogo321\Laravel2FA\Services\TwoFactorManager; public function boot() { $manager = app(TwoFactorManager::class); $manager->registerProvider('custom', new CustomProvider()); }
Customizing Views
After publishing the views, you can customize them at:
resources/views/vendor/two-factor/challenge.blade.phpresources/views/vendor/two-factor/settings.blade.phpresources/views/vendor/two-factor/recovery-codes.blade.phpresources/views/vendor/two-factor/emails/code.blade.php
Events
The package fires these events (you can create listeners for them):
// When 2FA is enabled event(new TwoFactorEnabled($user, $provider)); // When 2FA is disabled event(new TwoFactorDisabled($user, $provider)); // When 2FA challenge is passed event(new TwoFactorVerified($user, $provider));
Security Best Practices
- Always use HTTPS in production
- Enable rate limiting to prevent brute-force attacks (configured in
config/two-factor.php) - Store recovery codes securely - remind users to save them
- Use strong encryption - Laravel's built-in encryption is used for secrets
- Monitor failed attempts - consider logging failed 2FA attempts
- Educate users about phishing and social engineering
Testing
Run the package tests:
composer test
Advanced Usage Examples
Example: Custom TOTP Setup Controller
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Mortogo321\Laravel2FA\Facades\TwoFactor; class TwoFactorSetupController extends Controller { public function setupTotp(Request $request) { $user = $request->user(); // Generate credentials $credentials = TwoFactor::generateCredentials($user, 'totp'); // Store secret in session temporarily session(['totp_secret' => $credentials['secret']]); return view('two-factor.setup-totp', [ 'qrCode' => $credentials['qr_code'], ]); } public function verifyTotp(Request $request) { $request->validate([ 'code' => 'required|string|size:6', ]); $user = $request->user(); $secret = session('totp_secret'); // Verify the code if (!TwoFactor::verify($user, 'totp', $request->code)) { return back()->withErrors(['code' => 'Invalid code']); } // Enable TOTP TwoFactor::enable($user, 'totp', ['secret' => $secret]); session()->forget('totp_secret'); return redirect() ->route('two-factor.index') ->with('success', 'TOTP enabled successfully!'); } }
Example: Reusable Blade Component
Create a reusable component for 2FA status:
<!-- resources/views/components/two-factor-status.blade.php --> <div class="two-factor-status"> @if(auth()->user()->hasTwoFactorEnabled()) <div class="alert alert-success"> <strong>Protected</strong> Your account is secured with two-factor authentication. <a href="{{ route('two-factor.index') }}">Manage</a> </div> @else <div class="alert alert-warning"> <strong>Not Protected</strong> Enable two-factor authentication for better security. <a href="{{ route('two-factor.index') }}">Enable Now</a> </div> @endif </div>
Usage:
<x-two-factor-status />
Example: Requiring 2FA for Admin Users
// app/Policies/AdminPolicy.php public function accessAdmin(User $user) { // Require admin users to have 2FA enabled if ($user->isAdmin() && !$user->hasTwoFactorEnabled()) { return Response::deny('Admins must enable two-factor authentication.'); } return Response::allow(); }
Example: Implementing SMS with Twilio
- Install Twilio SDK:
composer require twilio/sdk
- Add credentials to
.env:
TWILIO_SID=your_sid TWILIO_TOKEN=your_token TWILIO_FROM=+1234567890
- Update
src/Providers/SmsProvider.phpin your published package files:
use Twilio\Rest\Client; protected function sendSms(Model $user, string $code): void { $method = $user->twoFactorMethods() ->where('provider', $this->getName()) ->first(); if (!$method || !isset($method->metadata['phone_number'])) { return; } $twilio = new Client( config('services.twilio.sid'), config('services.twilio.token') ); $twilio->messages->create( $method->metadata['phone_number'], [ 'from' => config('services.twilio.from'), 'body' => "Your 2FA code is: {$code}" ] ); }
- Configure in
config/two-factor.php:
'sms' => [ 'enabled' => true, 'driver' => 'twilio', 'code_length' => 6, 'code_expiry' => 5, ],
- Add Twilio config to
config/services.php:
'twilio' => [ 'sid' => env('TWILIO_SID'), 'token' => env('TWILIO_TOKEN'), 'from' => env('TWILIO_FROM'), ],
Example: Protect All Authenticated Routes
Instead of applying middleware to individual routes, protect all authenticated routes:
// app/Http/Kernel.php (Laravel 10) // or bootstrap/app.php (Laravel 11) protected $middlewareGroups = [ 'auth' => [ 'auth', 'two-factor', // Add this line ], ];
Testing
Writing Tests for 2FA
// tests/Feature/TwoFactorTest.php use Mortogo321\Laravel2FA\Facades\TwoFactor; public function test_user_can_enable_totp() { $user = User::factory()->create(); // Generate credentials $credentials = TwoFactor::generateCredentials($user, 'totp'); $this->assertArrayHasKey('secret', $credentials); $this->assertArrayHasKey('qr_code', $credentials); // Enable TOTP $result = TwoFactor::enable($user, 'totp', [ 'secret' => $credentials['secret'] ]); $this->assertTrue($result); $this->assertTrue(TwoFactor::isEnabled($user, 'totp')); } public function test_user_is_redirected_to_challenge_when_2fa_enabled() { $user = User::factory()->create(); // Enable 2FA TwoFactor::enable($user, 'totp', ['secret' => 'test-secret']); // Try to access protected route $response = $this->actingAs($user) ->get('/dashboard'); $response->assertRedirect(route('two-factor.challenge')); } public function test_user_can_verify_with_recovery_code() { $user = User::factory()->create(); // Enable 2FA and generate recovery codes TwoFactor::enable($user, 'totp', ['secret' => 'test-secret']); $recoveryService = app(RecoveryCodeService::class); $codes = $recoveryService->generate($user); // Verify with recovery code $this->assertTrue(TwoFactor::verify($user, 'recovery', $codes[0])); }
Run Package Tests
composer test
Production Checklist
Before deploying to production:
- Configure mail settings for email codes
- Set up SMS provider (if using SMS)
- Enable HTTPS (required for WebAuthn)
- Configure rate limiting in
config/two-factor.php - Test all providers you've enabled
- Backup recovery codes for your admin accounts
- Update privacy policy to mention 2FA data storage
- Train support team on recovery code process
- Set up monitoring for failed 2FA attempts
- Test recovery code workflow
- Add links to 2FA settings in your app's navigation
- Consider making 2FA mandatory for admin users
Troubleshooting
"Class TwoFactor not found"
Make sure you've added the trait to your User model and cleared cache:
php artisan config:clear php artisan cache:clear composer dump-autoload
QR Code not displaying
Make sure you have the required dependencies:
composer require bacon/bacon-qr-code
SMS not sending
Implement the SMS sending logic in SmsProvider::sendSms() with your preferred SMS service (Twilio, Vonage, etc.). See the SMS with Twilio example above.
WebAuthn not working
- Ensure you're using HTTPS (required for WebAuthn)
- Check browser compatibility
- Verify your domain is properly configured
2FA Challenge Not Showing
Verify middleware is applied:
Route::middleware(['auth', 'two-factor'])->group(function () { // Your routes });
Recovery Codes Not Generating
Ensure recovery codes are enabled in config:
'recovery' => [ 'enabled' => true, 'count' => 8, 'length' => 10, ],
FAQ
Q: Can users have multiple 2FA methods enabled at once?
A: Yes! Users can enable multiple providers (e.g., both TOTP and Email) and use any of them to authenticate.
Q: What happens if a user loses their 2FA device?
A: They can use one of their recovery codes to access their account, then disable and re-enable 2FA with a new device.
Q: Is WebAuthn/Passkey more secure than TOTP?
A: Both are secure, but WebAuthn is phishing-resistant because it's tied to your domain. TOTP codes can potentially be phished if users enter them on fake sites.
Q: Can I require 2FA for specific user roles only?
A: Yes, implement custom logic in middleware or policies:
if ($user->isAdmin() && !$user->hasTwoFactorEnabled()) { return redirect()->route('two-factor.index') ->with('error', 'Admins must enable 2FA'); }
Q: How long are email/SMS codes valid?
A: By default 5 minutes, configurable in config/two-factor.php under code_expiry.
Q: Can I use this with Laravel Breeze/Jetstream?
A: Yes! The package integrates seamlessly with any Laravel authentication system. Just add the middleware to your routes.
Q: Does this package send emails/SMS?
A: Email sending is built-in. For SMS, you need to implement the sending logic with your preferred provider (Twilio, Vonage, etc.).
Q: Can I customize the verification timeout?
A: Yes, in config/two-factor.php:
'session' => [ 'remember_duration' => 30, // days (0 to require 2FA on every login) ],
Q: How do I force all users to enable 2FA?
A: Create middleware that checks $user->hasTwoFactorEnabled() and redirects to setup if false.
Q: Is the package compatible with Laravel 11?
A: Yes! It supports Laravel 10 and 11.
Performance Considerations
- Database Queries: The package uses efficient queries with proper indexing
- Caching: Email/SMS codes are cached to prevent duplicate sends
- Encryption: Secrets are encrypted at rest using Laravel's encryption
- Rate Limiting: Built-in to prevent abuse without external dependencies
Contributing
Contributions are welcome! Here's how you can help:
- Fork the repository on GitHub
- Create a feature branch:
git checkout -b feature/my-new-feature - Make your changes and add tests if applicable
- Commit your changes:
git commit -am 'Add some feature' - Push to the branch:
git push origin feature/my-new-feature - Create a Pull Request on GitHub
Development Setup
git clone https://github.com/mortogo321/laravel-2fa.git cd laravel-2fa composer install composer test
Please ensure your code follows PSR-12 coding standards and includes tests for new features.
Changelog
See CHANGELOG.md for all changes and version history.
License
This package is open-sourced software licensed under the MIT license.
Credits
- Author: Mor
- Email: mortogo321@gmail.com
- GitHub: https://github.com/mortogo321/laravel-2fa
Support
For issues, questions, or feature requests, please open an issue on GitHub.