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

v1.0.0 2025-10-13 17:47 UTC

This package is auto-updated.

Last update: 2025-11-13 18:15:58 UTC


README

Latest Version on Packagist Total Downloads License PHP Version Laravel Version

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

  • 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)

  1. User enables TOTP from settings page
  2. QR code is displayed
  3. User scans with authenticator app
  4. User enters code to verify
  5. 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.php
  • resources/views/vendor/two-factor/settings.blade.php
  • resources/views/vendor/two-factor/recovery-codes.blade.php
  • resources/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

  1. Always use HTTPS in production
  2. Enable rate limiting to prevent brute-force attacks (configured in config/two-factor.php)
  3. Store recovery codes securely - remind users to save them
  4. Use strong encryption - Laravel's built-in encryption is used for secrets
  5. Monitor failed attempts - consider logging failed 2FA attempts
  6. 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

  1. Install Twilio SDK:
composer require twilio/sdk
  1. Add credentials to .env:
TWILIO_SID=your_sid
TWILIO_TOKEN=your_token
TWILIO_FROM=+1234567890
  1. Update src/Providers/SmsProvider.php in 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}"
        ]
    );
}
  1. Configure in config/two-factor.php:
'sms' => [
    'enabled' => true,
    'driver' => 'twilio',
    'code_length' => 6,
    'code_expiry' => 5,
],
  1. 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:

  1. Fork the repository on GitHub
  2. Create a feature branch: git checkout -b feature/my-new-feature
  3. Make your changes and add tests if applicable
  4. Commit your changes: git commit -am 'Add some feature'
  5. Push to the branch: git push origin feature/my-new-feature
  6. 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

Support

For issues, questions, or feature requests, please open an issue on GitHub.