vormkracht10/filament-two-factor-auth
Adds two-factor authentication to Filament panels. Users enable TOTP through authenticator apps with QR codes and recovery codes, while admins can enforce 2FA panel-wide for stronger login security.
v3.0.0 is a breaking release: the package moved to the Backstage organisation and the PHP namespace changed to Backstage\.... Treat this as a fresh install rather than a patch bump.
Minimal setup on a Filament panel:
backstagephp/filament-2fa; if your composer.json still points at the old vendor, swap it before running composer update).$table->text('two_factor_secret')->nullable();
$table->text('two_factor_recovery_codes')->nullable();
$table->timestamp('two_factor_confirmed_at')->nullable();
use Backstage\Filament2fa\Contracts\TwoFactorAuthentication;
use Backstage\Filament2fa\Traits\TwoFactorAuthenticatable;
class User extends Authenticatable implements FilamentUser, TwoFactorAuthentication
{
use TwoFactorAuthenticatable;
}
Double-check every use Baspa\... / old-namespace import in your app — these are the lines that break on upgrade.public function panel(Panel $panel): Panel
{
return $panel
->plugin(TwoFactor::make());
}
config:clear, optimize:clear, filament:optimize-clear).Where to look first: the plugin class and its fluent API (it's where enforcement, branding/icon, and page registration live), then config/filament-2fa.php. First use case: lock down an admin panel so every user must enrol a TOTP app before reaching any resource — enable it on your most privileged panel only, and roll out to staff before customers.
Per-panel registration, not global. Register TwoFactor::make() only on panels that need it (e.g. AdminPanelProvider), and leave the public/customer panel alone unless required. This keeps the login redirect behaviour scoped and makes it trivial to A/B the rollout.
Enforcement vs. self-service. Two distinct flows you'll typically ship together:
two_factor_confirmed_at and pushes them through setup. Ideal for internal/admin panels.Multi-guard / multi-model apps. If you have separate User and Admin models, implement the 2FA contract on each and add the columns to each table. Do not attempt to share the trait across guards with a single config entry.
Onboarding existing users. For a live app, don't flip enforcement to "on" repo-wide in one deploy. Ship the plugin with self-service enabled first, email/nudge users, then enable enforcement per panel. Users with two_factor_confirmed_at = null are the ones the middleware will redirect, so that column is your rollout dashboard — User::whereNull('two_factor_confirmed_at')->count().
Recovery codes are the support path. Store them encrypted (the trait handles this), never log them, and give support a documented process: user loses their phone → admin clears two_factor_secret / two_factor_recovery_codes / two_factor_confirmed_at via tinker or an admin action, user re-enrols. Build that reset action early; it's the #1 2FA support ticket.
Translations. v3 adds German and fixes translation overriding. If you run a non-English panel, publish the package translations and override keys in lang/vendor/... rather than editing vendor files — the override fix in this release makes published overrides take effect reliably, so re-check any language files you'd previously worked around.
Testing. In feature tests, either create users with two_factor_confirmed_at set, or disable enforcement for the test panel in TestCase/phpunit.xml. Always include one test that asserts an unenrolled user is redirected away from a protected resource — that's the regression you actually care about.
Baspa\... → Backstage\... touches your User model imports, panel provider plugin registration, any custom pages extending package classes, published config/view files that reference the service provider, and any container bindings or class_exists checks. Run a project-wide grep for the old namespace and the old vendor string before you composer update, and diff your published views afterwards.composer update resolves nothing new, confirm the require line and that composer.lock isn't pinned to the old vendor./, v2 could bounce you infinitely between login and the 2FA challenge. If you previously worked around this with custom middleware, route ordering, or by moving the panel path — remove that workaround. Keeping it may now cause the loop it was patching.text/longText — encrypted values are long, and a truncated varchar(255) produces "MAC is invalid" decryption errors that look like a config problem but are a schema problem.php artisan optimize:clear && php artisan filament:optimize-clear as the first debugging step — before you start reading stack traces.How can I help you explore Laravel packages today?