visanduma/nova-two-factor
visanduma/nova-two-factor adds two-factor authentication to Laravel Nova. It lets users enable TOTP-based 2FA with QR code setup and recovery codes, helping protect Nova access with an extra verification step at login.
Architecture fit. The package is narrowly and correctly scoped: it adds a second factor to the Laravel Nova admin panel only, via a Nova tool page (enrollment QR, TOTP secret, recovery codes) plus a middleware layer on the Nova route group and a trait on the user model. For a team already running Nova as its back-office surface, this slots into existing architecture rather than introducing a new identity system — no IdP, no SAML/OIDC, no new service to operate. That is the core appeal: a small, well-understood control closing an "is MFA enforced on administrative access?" gap. It is explicitly not end-user authentication, SSO, SCIM, WebAuthn/passkeys, or adaptive access, and should not be positioned as a step toward those on the roadmap.
Integration feasibility. The install path is conventional and low-ceremony — composer require, publish, migrate, add a trait to User, register the tool in NovaServiceProvider::tools(), and set an enforce flag. The real integration work is not installation but the surrounding workflow: onboarding nudges (the tool is opt-in and buried in the sidebar until enforcement is on), a recovery-code capture and regeneration story, an audit trail we own (the package's per-user enable/disable is a UI convenience, not a log), and a break-glass admin reset path the package does not provide. Estimated engineering effort is small for the happy path and materially larger for the reset runbook, testing, and rollout rehearsal.
Technical risk. This is a security control sourced from an unmaintained-looking dependency: zero stars, zero dependents, and no visible adoption. In an auth path, that means upstream deletion or a force-push is a live risk, so the effective decision is "review, pin, fork, and own" rather than "buy." Three risks dominate beyond supply chain:
catch blocks, so that class must be read line by line before enforcement is considered.APP_KEY rotation coupling. If the TOTP secret is stored with an encrypted cast, a routine key rotation invalidates every enrolled admin simultaneously, mid-incident. This must be confirmed from the migration before enrollment scales./nova/login — custom tools, package routes on the Nova middleware group, actions, and impersonation. Any route that authenticates a Nova user without clearing the challenge is a bypass.Secondary risks: no built-in rate limiting on a six-digit code (we add RateLimiter ourselves, 5 attempts / 1-minute decay, keyed on user + IP, with the counter not resetting on a correct password plus bad code); config cache making the enforce flag inert until the next deploy, which breaks it as a rollback mechanism; and duplicate enrollment UX if Fortify/Jetstream already writes two_factor_secret to the same model.
Key questions for the review. Which Nova guard does the middleware resolve, and is our Nova guard web? Is the secret encrypted, and what is our key-rotation procedure if it is? What is the exact session key asserted by the middleware (needed for tests)? Does a "remember this device" cookie exist, and what is its lifetime and revocation story? Which Nova-registered routes authenticate users outside the challenge? Who on the platform team owns this dependency, and does a second super-admin with 2FA enabled exist to invoke a reset?
Stack fit. Appropriate only where the stack matches the stated profile: Laravel 9–10 with Nova already serving internal/admin access, a standard session guard, and no existing MFA on the same user model. Poor fit for multi-guard or tenant-scoped user resolution (needs a spike, not an assumption), and a hard no where the organization requires vendor SLA and certification — there is no vendor to call.
Migration path. There is no data migration burden — this is greenfield enrollment, not a cutover. The migration path that matters is the enforcement path: off → on → (simulated incident) → off in staging, rehearsed at least once so rollback is an action rather than an improvisation. php artisan config:clear is part of that rehearsal, given config cache. Before enforcement, the plan should include a database-driven enforcement setting or an explicit config-cache rebuild step in the rollback runbook — decide which, deliberately.
Compatibility. Pin the package version and the Nova version as an explicit, tested pair in the dependency register; thin packages typically break on the next Nova/Laravel cycle, not on install day. Vendor or fork into our own registry before enforcement is switched on. Budget roughly one engineering day per upgrade cycle to carry the fork, plus the risk of a rushed patch on security-sensitive code.
Sequencing. The ordering is non-negotiable and should be written into the delivery plan as gates:
TwoFactorProvider interface with exactly one implementation calling Visanduma\NovaTwoFactor — this is the highest-value day-to-day decision, because replacing the package or patching a Nova break becomes one class, not every call site.--force plus reason, logged with operator, subject, timestamp, incident reference)./nova before a valid code is posted — and keep it in our repo, not the package's.Maintenance. An unowned auth dependency is an unmaintained control. This needs a named owner on the platform team, a forked source in our registry, a pinned version pair, and a standing line item for upgrade tax. The package's UI is unlikely to evolve; our fork will accumulate the patches.
Support. Two support surfaces are created and neither is delivered by the package: helpdesk triage for device loss (scripted, with an escalation path to the break-glass operator) and the reset runbook itself, tested end to end before enforcement. The reset command is only useful if someone other than the locked-out user can run it, which implies a second super-admin with 2FA enabled and a documented contact.
Scaling. Enrollment scales trivially; the operational cost is per-user support, not per-request traffic. Two scaling limits to note: the control is admin-only, so it does not scale to or substitute for customer-facing MFA; and recovery-code handling (shown once, regeneration path) is the most likely source of recurring tickets. Rate limiting on the challenge endpoint must be added by us and reviewed as enrollment grows.
Failure modes. Rank them by silence, not by severity of error:
config() after a cache warm, exception in middleware. Each must be tested deliberately; each must not reach the dashboard.APP_KEY rotation — confirm encryption, then pre-plan either column re-encryption as a rotation step or a reset-all-and-re-enroll script. key:generate should never be run casually on an enforced install.Ramp-up. Roughly a week of elapsed time for a competent Laravel engineer, dominated by reading the middleware, writing the seam and reset command, and the staging rehearsal — not by installation. Add helpdesk training on device-loss triage and 48 hours of watching challenge-endpoint error rate immediately after enforcement. The decision framing for stakeholders: this is a fast, cheap way to demonstrate MFA on privileged accounts where compliance pressure exists, and a deferred item where it does not — because the true cost of adoption is the ownership burden (fork upkeep, reset runbook, break-glass coverage, key-rotation coupling), not the composer require.
How can I help you explore Laravel packages today?