Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Two Factor Laravel Package

gabrielesbaiz/nova-two-factor

Adds two-factor authentication to Laravel Nova. Protect your admin panel with TOTP-based 2FA: users enroll authenticator apps, verify one-time codes at login, and manage recovery codes from a familiar Nova interface. Lightweight and easy to install and configure.

View on GitHub
Deep Wiki
Context7

Getting Started

gabrielesbaiz/nova-two-factor adds TOTP-based two-factor authentication (Google Authenticator, 1Password, Authy) to Laravel Nova's login flow. It is a Nova add-on, not a general app-wide 2FA solution — it hooks the Nova guard and only protects /nova logins.

Install and publish in one pass:

composer require gabrielesbaiz/nova-two-factor
php artisan vendor:publish --provider="Gabrielesbaiz\NovaTwoFactor\NovaTwoFactorServiceProvider"
php artisan migrate

Where to look first, in this order:

  1. vendor/gabrielesbaiz/nova-two-factor/config/ — the published config file is the source of truth for middleware aliases, column names, and enforcement flags. Read it before writing any code; the README can lag behind a minor release like 2.2.0.
  2. vendor/gabrielesbaiz/nova-two-factor/database/migrations/ — confirm exactly which columns get added to users (typically two_factor_secret, two_factor_recovery_codes, two_factor_confirmed_at). This determines what you can safely expose in your own admin tooling.
  3. config/nova.php → middleware array — this is where the package's 2FA middleware is registered and where 90% of "it's not prompting" bugs live.

First practical use case: protect a client-facing Nova admin panel. A developer on the team logs into /nova, is redirected to a setup screen, scans a QR code, confirms a 6-digit code, and is issued recovery codes. From that point the login flow requires a TOTP code. Before rolling to users, run it end-to-end on a staging deploy with APP_ENV=staging so you verify the QR renders correctly behind your actual proxy/asset host — a broken QR image is the most common first-run failure.

Implementation Patterns

Add the trait to your authenticatable model. Typically App\Models\User (or a dedicated NovaUser if you have a separate admin guard):

use Gabrielesbaiz\NovaTwoFactor\TwoFactorAuthenticatable;

class User extends Authenticatable
{
    use TwoFactorAuthenticatable;
}

Register the middleware per-environment. Put the 2FA middleware into config/nova.php's middleware array rather than app/Http/Kernel.php — Nova's route group is where it belongs, and keeping it out of the global stack avoids forcing TOTP on your public-facing auth.

Gate enforcement behind a flag. For staged rollouts, wrap the middleware registration in a config lookup (or a Feature::active() check) so you can enable 2FA for staff first and customers later:

// config/nova.php
'middleware' => array_merge($default, [
    config('nova-two-factor.enforce') ? \Gabrielesbaiz\NovaTwoFactor\Http\Middleware\TwoFactorAuthenticate::class : null,
]),

Handle the reset path as a first-class workflow. Users lose phones. Build an artisan command or Nova action that nulls the 2FA columns for a user, and make it require a reason/audit log. A php artisan tinker one-liner is fine for you, but it should not be the documented process for support staff.

User::where('email', $email)->update([
    'two_factor_secret' => null,
    'two_factor_recovery_codes' => null,
    'two_factor_confirmed_at' => null,
]);

Expose 2FA status in Nova. Since the columns land on your User resource, show two_factor_confirmed_at as a Badge/Boolean field on the user detail view. Support tickets get resolved in seconds when an admin can see whether 2FA is actually active.

Test it without fighting it. In Pest/PHPUnit, either acting-as a user already marked confirmed (skip the challenge) or fake the middleware. Never let feature tests hit the real TOTP window — it will flake in CI at minute boundaries:

$this->actingAs($user = User::factory()->withTwoFactor()->create())
     ->withoutMiddleware(\Gabrielesbaiz\NovaTwoFactor\Http\Middleware\TwoFactorAuthenticate::class)
     ->get('/nova');

For the setup flow itself, assert the redirect to the setup route and the presence of the QR payload rather than scanning it.

Pair with a session/device policy. The package verifies at login; it does not change session lifetime. If your threat model includes stolen laptops, add a shorter Nova session lifetime or a "remember this device" expiry in the same config block so TOTP prompts are not defeated by a 30-day remember cookie.

Gotchas and Tips

  • Minor releases here are frequent and sparsely documented. 2.2.0 ships with nothing but a compare link, so treat the diff as the changelog: composer update gabrielesbaiz/nova-two-factor --dry-run, then read git diff 2.1.2..2.2.0 in vendor/ before deploying. Pay attention to migration changes — a modified published migration in a minor bump is the classic source of "works on fresh install, breaks on upgrade."
  • Published migrations are snapshots. If the package changes a column definition in 2.2.x, your already-published migration will not update. Diff the vendor migration against yours after every upgrade.
  • APP_KEY rotation silently invalidates every TOTP secret. If the package encrypts two_factor_secret with Laravel's encrypter (it uses the standard cast), rotating keys without php artisan key:rotate handling bricks 2FA for all users. Plan key rotation and a mass reset together.
  • Time drift is the top user-facing complaint. TOTP allows ±1 window by default. If your DB/app servers and users' phones disagree — common in containers with no NTP — codes fail intermittently. Run timedatectl in the container and document a fallback to recovery codes.
  • Recovery codes are single-use and easy to exhaust. Make sure the flow gives clear feedback when a code is consumed, and add a "regenerate recovery codes" screen behind the normal 2FA challenge (not just login).
  • Nova middleware order matters. If another middleware short-circuits (redirects unauthenticated users, or a custom SSO middleware) before the 2FA middleware runs, users slip through. Position the 2FA middleware immediately after Nova's authentication middleware and verify with a request that has no two_factor_confirmed_at set.
  • Octane and long-running workers. Nothing in the 2FA flow should be cached in-process; if you memoise user 2FA state in a singleton, reset it per request or you'll randomly skip challenges under Octane/Swoole.
  • Extension points: the setup/verify controllers are the natural override surface if you need custom branding on the challenge screen. Rather than editing vendor files, publish views (if supported) or subclass the controller and rebind it in a service provider — check the provider's register() for what is bound to the container before assuming you need a fork.
  • Don't confuse this with app-wide 2FA. If a compliance requirement covers all logins (not just Nova), this package is a stopgap. Laravel Fortify or a dedicated 2FA package on the main guard is the right answer; you can run both, but keep the secrets separate and document which system owns which user's second factor.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource