PHPackages                             olusegun171/laravel-mfa - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. olusegun171/laravel-mfa

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

olusegun171/laravel-mfa
=======================

Multi-factor authentication for Laravel — TOTP compatible with Google Authenticator, Authy, and any RFC 6238 app.

017PHPCI passing

Since May 12Pushed 4w agoCompare

[ Source](https://github.com/olusegun171/laravel-mfa)[ Packagist](https://packagist.org/packages/olusegun171/laravel-mfa)[ RSS](/packages/olusegun171-laravel-mfa/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependenciesVersions (1)Used By (0)

laravel-mfa
===========

[](#laravel-mfa)

Multi-factor authentication for Laravel. Works with Google Authenticator, Authy, 1Password, Bitwarden, and any other RFC 6238 compatible app.

[![Tests](https://github.com/olusegun171/laravel-mfa/actions/workflows/tests.yml/badge.svg)](https://github.com/olusegun171/laravel-mfa/actions/workflows/tests.yml)[![Latest Version on Packagist](https://camo.githubusercontent.com/26ae7e5a676d85a166325ed516bbebb3ced6dfa8e1e1f4a621d9a36437258dd6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6f6c75736567756e3137312f6c61726176656c2d6d66612e737667)](https://packagist.org/packages/olusegun171/laravel-mfa)[![Total Downloads](https://camo.githubusercontent.com/a5d014c3073032731908aa9450f7608dde23f30f9c3d19a7f8300e520df9d839/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6f6c75736567756e3137312f6c61726176656c2d6d66612e737667)](https://packagist.org/packages/olusegun171/laravel-mfa)[![PHP](https://camo.githubusercontent.com/1a5e13126d38c1d05f712dae30e7f60ae0444a9c882e9e526349ccba27facb8d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d253345253344382e312d626c7565)](https://php.net)[![Laravel](https://camo.githubusercontent.com/94e9c8450999913235efa10e8b4593c4cc12190aee2829d4e12fef3a1b0cb2df/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31302532463131253246313225324631332d726564)](https://laravel.com)[![License: MIT](https://camo.githubusercontent.com/784362b26e4b3546254f1893e778ba64616e362bd6ac791991d2c9e880a3a64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e737667)](LICENSE)

---

Features
--------

[](#features)

- TOTP codes — RFC 6238 compliant, 6-digit, 30-second window
- QR code URI generation for any authenticator app
- AES-256-CBC encrypted secret storage
- 8 bcrypt-hashed one-time recovery codes
- Clock-drift tolerance (±1 time-step)
- `TwoFactor` facade + `HasTwoFactor` Eloquent trait

---

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 10, 11, 12, or 13

---

Installation
------------

[](#installation)

```
composer require olusegun171/laravel-mfa
```

The service provider and `TwoFactor` facade are registered automatically via package auto-discovery.

---

Setup
-----

[](#setup)

### 1. Publish the config

[](#1-publish-the-config)

```
php artisan vendor:publish --tag=two-factor-config
```

### 2. Run the migration

[](#2-run-the-migration)

```
# Resolves the table from the guard's Eloquent model automatically
php artisan two-factor:install --guard=web

# Or pass the table directly
php artisan two-factor:install --table=admins

php artisan migrate
```

This adds three nullable columns to your users table:

ColumnDescription`two_factor_secret`AES-256-CBC encrypted TOTP secret`two_factor_recovery_codes`JSON array of bcrypt-hashed one-time backup codes`two_factor_confirmed_at`Timestamp set when the user confirms their first code### 3. Add the trait to your model

[](#3-add-the-trait-to-your-model)

```
use Olusegun171\TwoFactor\Traits\HasTwoFactor;

class User extends Authenticatable
{
    use HasTwoFactor;
}
```

---

Usage
-----

[](#usage)

See the [Integration](#integration) section for full usage examples split by authenticated and unauthenticated context.

---

Status Helpers
--------------

[](#status-helpers)

```
TwoFactor::remainingRecoveryCodes($user); // number of unused backup codes

// Model methods via HasTwoFactor trait
$user->hasTwoFactorEnabled();  // true once two_factor_confirmed_at is set
$user->hasTwoFactorPending();  // true if setup started but not yet confirmed
```

---

QR Code Identifier
------------------

[](#qr-code-identifier)

By default the QR code label uses `getAuthIdentifier()` — typically the user's primary key. To show something friendlier (like an email address) in the authenticator app, add `getTwoFactorIdentifier()` to your model:

```
class User extends Authenticatable
{
    use HasTwoFactor;

    public function getTwoFactorIdentifier(): string
    {
        return $this->email;
    }
}
```

The label will appear as `YourApp:user@example.com` inside the authenticator app.

---

Configuration
-------------

[](#configuration)

```
// config/two-factor.php
return [
    'issuer' => env('MFA_ISSUER', null), // shown in authenticator apps; defaults to app name

    'totp' => [
        'digits'    => 6,
        'period'    => 30,   // seconds per time-step
        'window'    => 1,    // ±1 period tolerance for clock drift
        'algorithm' => 'sha1',
    ],
];
```

---

Security Notes
--------------

[](#security-notes)

- **Rate-limit** the challenge endpoint — 5 attempts per minute is a reasonable starting point.
- **Serve over HTTPS** — codes in transit must be encrypted.
- **Recovery codes are shown once** — only bcrypt hashes are stored in the database.
- All comparisons use `hash_equals()` for constant-time evaluation.
- TOTP secrets are encrypted with AES-256-CBC using a 32-byte slice of your `APP_KEY`.
- **Never log** `two_factor_secret` or `two_factor_recovery_codes`.

---

Integration
-----------

[](#integration)

---

### Authenticated context (settings or an enforced page)

[](#authenticated-context-settings-or-an-enforced-page)

The user is already logged in. They enable 2FA from their account settings or a dedicated page to enforce the 2fa, scan the QR code, and confirm with their first code.

**Enable and show the QR code**

```
$data = TwoFactor::generate($user);

// Pass $data to your view:
// $data['qr_code_url']    —
// $data['secret']         — manual entry fallback
// $data['recovery_codes'] — show once, store somewhere safe
```

**Confirm the first code**

```
try {
    TwoFactor::confirm($user, $request->code);
} catch (InvalidCodeException $e) {
    return back()->withErrors(['code' => $e->getMessage()]);
}
```

**Disable 2FA**

```
TwoFactor::disable($user);
```

**Regenerate recovery codes**

```
$codes = TwoFactor::regenerateRecoveryCodes($user); // string[]
```

---

### Unauthenticated context (login flow)

[](#unauthenticated-context-login-flow)

The user is not yet logged in. `Auth::login()` is not called until the 2FA code is verified — the user is fully unauthenticated between the password step and the code step.

**Step 1 — Password check (`LoginController`)**

```
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Olusegun171\TwoFactor\Facades\TwoFactor;

$user = User::where('email', $request->email)->first();

if (!$user || !Hash::check($request->password, $user->password)) {
    return back()->withErrors(['email' => 'Invalid credentials.']);
}

if (TwoFactor::requiresChallenge($user)) {
    return redirect()->route('two-factor.challenge');
}

// 2FA not set up — enforced: require setup before granting access
$setup = TwoFactor::setup($user);
return redirect()->route('two-factor.setup')->with('setup', $setup);
```

**Step 2 — Challenge routes**

Wrap the challenge routes with the `two-factor` middleware so they redirect to login if accessed directly (no pending session).

```
Route::middleware('two-factor')->group(function () {
    Route::get('/two-factor/challenge',  [TwoFactorChallengeController::class, 'show'])->name('two-factor.challenge');
    Route::post('/two-factor/challenge', [TwoFactorChallengeController::class, 'store']);
    Route::post('/two-factor/recovery',  [TwoFactorChallengeController::class, 'recover']);
});
```

**Step 3 — Challenge controller**

```
use Olusegun171\TwoFactor\Exceptions\InvalidCodeException;
use Olusegun171\TwoFactor\Facades\TwoFactor;

// Submit a TOTP code
public function store(Request $request)
{
    $user = TwoFactor::getPendingUser();

    try {
        TwoFactor::verify($user, $request->code);
    } catch (InvalidCodeException $e) {
        return back()->withErrors(['code' => $e->getMessage()]);
    }

    TwoFactor::completeChallenge();
    Auth::login($user);
    $request->session()->regenerate();

    return redirect()->intended('/dashboard');
}

// Submit a recovery code instead
public function recover(Request $request)
{
    $user = TwoFactor::getPendingUser();

    try {
        TwoFactor::verifyRecoveryCode($user, $request->recovery_code);
    } catch (InvalidCodeException $e) {
        return back()->withErrors(['recovery_code' => $e->getMessage()]);
    }

    TwoFactor::completeChallenge();
    Auth::login($user);
    $request->session()->regenerate();

    return redirect()->intended('/dashboard');
}
```

`completeChallenge()` clears the pending session state. The caller is responsible for `Auth::login()` and `session()->regenerate()`.

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE)

###  Health Score

22

—

LowBetter than 22% of packages

Maintenance62

Regular maintenance activity

Popularity8

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/21316856?v=4)[Olusegun](/maintainers/olusegun171)[@olusegun171](https://github.com/olusegun171)

---

Top Contributors

[![olusegun171](https://avatars.githubusercontent.com/u/21316856?v=4)](https://github.com/olusegun171 "olusegun171 (8 commits)")

### Embed Badge

![Health badge](/badges/olusegun171-laravel-mfa/health.svg)

```
[![Health](https://phpackages.com/badges/olusegun171-laravel-mfa/health.svg)](https://phpackages.com/packages/olusegun171-laravel-mfa)
```

###  Alternatives

[kartik-v/yii2-password

Useful password strength validation utilities for Yii Framework 2.0

761.2M17](/packages/kartik-v-yii2-password)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
