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.
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:
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.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.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.
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.
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."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.timedatectl in the container and document a fallback to recovery codes.two_factor_confirmed_at set.register() for what is bound to the container before assuming you need a fork.How can I help you explore Laravel packages today?