oluomotoso/nova-google2fa
Add Google Two-Factor Authentication to Laravel Nova. This package brings TOTP-based 2FA to your Nova login flow, letting users enroll with an authenticator app via QR code and confirm a one-time code at sign-in for stronger account security.
composer require oluomotoso/nova-google2fa
php artisan vendor:publish --provider="Olumotoso\NovaGoogle2FA\Google2FAServiceProvider"
php artisan migrate
app/Providers/NovaServiceProvider.php inside tools():
public function tools()
{
return [
new \Olumotoso\NovaGoogle2FA\Google2FA,
];
}
php artisan nova:publish and php artisan optimize:clear) and hard-refresh the Nova UI so the new tool appears in the sidebar.Where to look first:
config/google2fa.php — window tolerance, issuer name, secret column/table, recovery-code toggle.users or a dedicated table; this shapes the rest of your integration.First use case: require Google Authenticator for every Nova admin. Assign the tool to a test user, enroll via QR, log out, log back in, and confirm the challenge appears before the dashboard. Do this on a staging deploy before touching production users.
Enrollment-then-enforcement middleware. The typical shape is a middleware that (a) lets the user through if they have no secret and are on the setup tool, (b) redirects enrolled-but-unverified users to the challenge screen, and (c) blocks everyone else from Nova routes until the session flag is set. Register it in config/nova.php (or Nova::middleware([...]) in NovaServiceProvider::booted()), after Nova's own Authenticate middleware so you always have a resolved user.
Secret storage via trait/column. If the package ships a trait, apply it to App\Models\User and cast the secret column as encrypted. Wrap the accessor so anything reading the column gets plaintext, but the DB row is ciphertext. If you're on a dedicated google2fa_secrets table, key the relation off user_id and delete the row on disable so re-enrollment starts clean.
Setup flow as a Nova tool. The tool page renders the QR (via bacon/bacon-qr-code through pragmarx/google2fa), shows the plaintext secret as a fallback, and asks for a 6-digit code to confirm before persisting. Do not persist the secret until confirmation succeeds — otherwise a user who abandons setup gets locked out by your own middleware.
Session verification flag. After a successful code check, the middleware should set a session key (e.g. nova_2fa_verified_at) scoped to the user ID. Bind it to the user so a logout/login cycle re-challenges, and so a session hijack on a shared machine doesn't inherit 2FA state.
Recovery codes. Generate them at enrollment, hash them before storage, show plaintext exactly once, and consume-on-use. This is your only reliable escape hatch short of a DB update.
Testing. Freeze time and generate a valid code in feature tests:
Carbon::setTestNow('2024-01-01 12:00:00');
$code = Google2FA::getCurrentOtp($user->google2fa_secret);
$this->actingAs($user)->post('/nova/2fa/verify', ['code' => $code]);
Also test the "wrong code" and "expired code" paths — those are where middleware ordering bugs surface.
Reusing the primitive outside Nova. If you have a customer-facing app on the same users table, use pragmarx/google2fa (or google2fa-laravel) directly with the same secret column rather than trying to import the Nova middleware. Keep the two enforcement layers in sync through a shared trait or policy.
carloscgo/nova-google2fa. Bug fixes and Nova API changes land upstream first. Pin a specific commit in composer.json and subscribe to the parent repo's issues; a dev-main reference here is a production liability. If the fork goes stale, be prepared to vendor the diff yourself — it's usually a handful of Nova 4 class/interface updates.public function boot(), public function render(), menu registration via Menu). If the tool renders blank, run php artisan nova:publish --force and inspect the compiled JS in public/vendor/nova — stale assets are the most common "it installed but does nothing" failure.NOVA_2FA_ENFORCE=false) that the middleware checks. You will need it during a bad deploy.pragmarx/google2fa accepts a window of 0 by default. Set window => 1 (or 2) in config so codes from slightly-off phone clocks still pass. Don't go higher than 2 — you're trading security for user support tickets.Authenticate, $request->user() is null and the middleware silently no-ops or 500s. Verify it appears after Nova's auth stack in the resolved middleware list (php artisan route:list -v on a Nova route).encrypted cast. Add the column to $hidden on the model and scrub it from any request-logging middleware.extends the package's and override the handle() decision. That keeps the fork replaceable if a maintained alternative appears.How can I help you explore Laravel packages today?