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

Two Factor Authentication Filament Laravel Package

liberusoftware/two-factor-authentication-filament

Adds two-factor authentication to Filament panels. Users enrol via an authenticator app, confirm with a one-time code, and store recovery codes. Includes challenge screens for login, profile pages for enabling or disabling 2FA, and configurable enforcement per panel.

View on GitHub
Deep Wiki
Context7

Getting Started

Preflight (do this before writing any code). This is a zero-star, zero-dependent package with a NOASSERTION license. Before it touches a production composer.json:

  1. Open the repo and read LICENSE (or the absence of one). NOASSERTION from Packagist means the manifest declares a license Composer can't map to an SPDX identifier — it may be proprietary, may be MIT with a typo, or may be missing entirely. If there is no license file, you have no grant to redistribute it.
  2. Read composer.json to confirm which Filament major it targets (filament/filament: ^3.0 vs ^4.0) and which core package it wraps. A "presentation adapter" almost always depends on a sibling domain module — likely liberusoftware/two-factor-authentication or similar — that owns the TOTP secret generation, recovery codes, and the TwoFactorAuthenticatable trait. The adapter is the UI; the sibling is the logic. Install and understand both.
  3. Because there are no dependents, treat the package as a reference implementation rather than a dependency. If you adopt it, pin an exact commit ("liberusoftware/...": "dev-main#abc1234") or fork it into your own VCS.

Install.

composer require liberusoftware/two-factor-authentication-filament
php artisan vendor:publish --tag="two-factor-authentication-filament-config"
php artisan vendor:publish --tag="two-factor-authentication-filament-migrations"
php artisan migrate

Tag names are guesses until you check the service provider's publishes() calls — run php artisan vendor:publish with no tag and read the interactive list.

Where to look first, in order:

File Why
src/*ServiceProvider.php What it registers, publishes, and whether it auto-discovers
src/*Plugin.php (if present) The Filament plugin contract — this is your integration point
src/Filament/Pages/ or src/Livewire/ The enrolment / challenge components
resources/views/ QR code rendering, recovery-code display
config/*.php Issuer name, window/drift, recovery-code count
composer.json require The core module you actually depend on

First use case — enrol the current user. Register the plugin on a panel, log in as a test user, navigate to the 2FA page, scan the QR with an authenticator app, submit the 6-digit code, and confirm the secret is persisted encrypted and recovery codes are shown exactly once. Do this on a throwaway SQLite database before touching a real app — if the package is broken, you want to find out in five minutes, not after wiring up middleware.

Implementation Patterns

Panel plugin registration. Liberu modules conventionally expose a Filament plugin class. Register it per-panel in the PanelProvider so admin and app panels can diverge:

public function panel(Panel $panel): Panel
{
    return $panel
        ->id('admin')
        ->plugin(\LiberuSoftware\TwoFactorAuthenticationFilament\TwoFactorAuthenticationPlugin::make()
            ->enableRecoveryCodes()
        );
}

Attach the enrolment UI to the profile page. Filament's built-in profile page is the natural host. If the package ships a standalone page, link to it from the user menu via a render hook rather than forking the panel layout:

use Filament\Support\Facades\FilamentView;

FilamentView::registerRenderHook(
    'panels::user-menu.before',
    fn () => view('partials.two-factor-menu-item'),
);

Keep the trait on the user model. The adapter renders; the core module enforces. Your User model needs the authenticatable contract regardless:

class User extends Authenticatable implements FilamentUser, TwoFactorAuthenticatable
{
    use HasFactory, Notifiable, TwoFactorAuthentication;
}

Enforcement lives in the login pipeline, not the page. The pattern that works: a Filament authenticateUsing() callback returns the user, then a middleware or a custom login response checks $user->hasEnabledTwoFactor() and redirects to a challenge route before any panel page renders. The adapter's job ends at "the user has a secret and confirmed it."

// App\Http\Middleware\RequireTwoFactor
if (auth()->check() && ! auth()->user()->hasEnabledTwoFactor() && ! $request->routeIs('filament.*.auth.two-factor.*')) {
    return redirect()->route('filament.admin.auth.two-factor.challenge');
}

Scope by guard when you have multiple user tables. Admin and customer panels usually share the trait but not the policy. Resolve the challenge component against the active guard explicitly; do not assume auth()->user() is the panel's guard user on shared layouts.

Compose with the core module's events. Enrolment typically fires something like TwoFactorAuthenticationEnabled / Disabled. Hook those for audit logging and notification emails rather than polling the DB:

Event::listen(TwoFactorAuthenticationEnabled::class, function ($event) {
    activity()->performedOn($event->user)->log('2fa.enabled');
});

Testing pattern. Feature-test the page with Livewire, and generate valid TOTP codes in-test rather than mocking the verify call:

use PragmaRX\Google2FA\Google2FA;

Livewire::actingAs($user)
    ->test(EnableTwoFactor::class)
    ->call('confirm', (new Google2FA)->getCurrentOtp($user->two_factor_secret))
    ->assertHasNoErrors();

Freeze time with $this->travelTo() when asserting recovery-code rotation or expiry.

Gotchas and Tips

The license is the real blocker. NOASSERTION plus zero dependents means no one has run a legal review on this. If it ships in a client product, get a human to read the LICENSE file first. Forking to an internal namespace does not remove the copyright question.

Presentation-only means presentation-only. Do not assume this package rate-limits the challenge, regenerates the session on success, or invalidates trusted devices on disable. Those are the security-relevant behaviours you must verify yourself. Explicitly call session()->regenerate() after a successful 2FA check to close session-fixation windows.

Secret leakage into the Livewire payload. Enrolment components hold the plaintext secret in a public property so the QR can be re-rendered. Check the component for #[Locked] or protected on that property — if it's public and un-locked, the secret round-trips to the browser on every Livewire request. Wrap it in an encrypted cast at the model layer and never log the component's state.

Encryption key rotation invalidates every enrolment. If two_factor_secret uses Laravel's encrypted cast and you rotate APP_KEY, every user silently loses their authenticator binding. Add a key:rotate runbook that forces 2FA re-enrolment, and monitor decryption exceptions — they surface as 500s on the profile page, not as clean failures.

Time drift will generate "invalid code" support tickets. Expose the verification window in config (typically ±1 or ±2 steps / 30s). A window of 0 is correct but hostile to desktop users with slow clocks; ±1 is the usual compromise. Document the NTP requirement in your internal runbook.

Recovery codes are single-display. Most implementations show them once, at confirmation. If your view re-queries them on refresh, or if you cache the enrolment page, users will screenshot-and-lose them. Add an explicit "regenerate recovery codes" action with password confirmation.

Filament major-version friction. If the package targets v3 and your app is on v4, Page::$view, ->form() vs ->schema(), and action namespaces will all break. Check before installing; there is no compat layer worth the effort for a zero-star package.

Cached components after adding a page. Run php artisan filament:optimize-clear (or optimize:clear) after registering the plugin. A stale bootstrap/cache/ will produce "page not found" during panel routing and send you chasing phantom route issues.

Don't run two 2FA stacks. If you also have Fortify's two_factor or a Breezy/Jetstream variant, you'll get duplicate middleware on the same routes, conflicting two_factor_secret columns, and doubled notifications. Pick one owner of the trait and disable the other's features in config/fortify.php.

Multi-panel enforcement mismatch. It's easy to enable the plugin on the admin panel and forget the customer panel, leaving a hole for the exact users who need it most. Write a test that iterates Filament::getPanels() and asserts 2FA middleware is present on each authenticated panel.

Vendor it if you depend on it. With zero dependents and a single maintainer, an upstream force-push or deletion is a real risk. Commit a patches/ entry with vcs-patches or mirror the tree into app/Modules/ so your build is reproducible.

Extension point worth knowing. If the shipped views don't match your design system, override via ->views() on the plugin or publish to resources/views/vendor/two-factor-authentication-filament/. Don't edit vendor/ — a composer update wipes it and you'll debug the regression twice.

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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core