marjose123/filament-lockscreen
Filament Lockscreen adds a secure lock screen to your Filament admin panel, automatically locking idle sessions and requiring the user's password to unlock. It helps protect sensitive admin pages when a session is left unattended, with configurable timeouts and a simple, native-looking overlay.
The package bolts a "lock" state onto an authenticated Filament session: the user stays logged in, but every panel route redirects to a lock screen until they re-enter their password.
Install and publish
composer require marjose123/filament-lockscreen
php artisan vendor:publish --tag="filament-lockscreen-config"
Also check vendor/marjose123/filament-lockscreen/database/migrations — if the installed version ships migrations (some releases persist lock state, others are session-only), publish and run them:
php artisan vendor:publish --tag="filament-lockscreen-migrations"
php artisan migrate
Register the plugin on the panel(s) you want locked
use MarJose123\FilamentLockscreen\Lockscreen;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(Lockscreen::make());
}
Add the trait to your authenticatable model (if the version you install requires it):
use MarJose123\FilamentLockscreen\Traits\Lockscreen;
class User extends Authenticatable
{
use Lockscreen;
}
Clear caches — Filament plugins register pages/assets at boot, so php artisan filament:upgrade (or optimize:clear) after install avoids "route not found" surprises.
Where to look first: the published config/filament-lockscreen.php (timeout, idle management, rate limiting, icon/UI toggles) and the Lockscreen::make() fluent methods on the plugin class. Those two files define 90% of the behaviour you'll want to change.
If you're upgrading to 3.4.1: read the config diff before overwriting. v3.4.1 reworked session idle management (PR #105), which means new/moved idle-related keys are likely. Don't blindly --force republish over a tuned config — publish to a scratch path or diff against vendor/marjose123/filament-lockscreen/config/.
First use case: log in to /admin, open the user menu — you should see a "Lock" action. Click it, confirm you can no longer navigate to any panel page, then unlock with your password and confirm you land back where you were. Then leave the tab alone for longer than your configured idle window and confirm it locks on its own.
Panel-scoped registration. Put ->plugin(Lockscreen::make()) only on panels that need it. In multi-panel apps (e.g. Admin + App), registering globally means a user locking the admin panel can get bounced when using an unrelated panel — decide explicitly whether the lock is per-session or per-panel and test the round trip.
Config-first tuning. Keep policy in config/filament-lockscreen.php rather than hard-coding in the panel provider, so per-environment differences (e.g. shorter idle windows in staging) come from .env:
// config/filament-lockscreen.php
'enable_rate_limit' => true,
'rate_limit' => 5, // unlock attempts
Programmatic locking. Beyond the built-in menu action, call the plugin's lock entry point from your own Filament action so it fits your UX (e.g. a "Step away" button on a dashboard widget, or a keyboard shortcut):
use MarJose123\FilamentLockscreen\Facades\Lockscreen; // verify namespace in the release you pin
Action::make('stepAway')
->action(fn () => Lockscreen::lock())
->requiresConfirmation();
Check the plugin class for the exact facade/helper name — this is the one API most likely to shift between releases, so wrap it in your own thin service class and call that instead of the package directly.
Idle-triggered locking. As of 3.4.1 the package manages session idle state itself and no longer locks prematurely, so start with the built-in idle configuration before writing your own. Look for the idle/lock timeout keys in the published config and drive them from .env. Only fall back to a custom Alpine timer + lock route if the built-in window granularity doesn't fit (e.g. per-panel different timeouts, or "warn at 4 min, lock at 5 min" UX):
->renderHook(
PanelsRenderHook::BODY_END,
fn () => view('filament.hooks.idle-lock'), // only if built-in idle doesn't cover you
)
If you do add a client-side timer, make sure it doesn't double up with the package's own idle detection — two mechanisms with different thresholds produce confusing, inconsistent lock times. Either way, keep enforcement server-side — a user can disable JS.
Pair with a real session lifetime. Locking is an overlay, not a logout. Set SESSION_LIFETIME and Laravel's AuthenticateSession middleware so an abandoned browser eventually dies for real. In bootstrap/app.php:
$middleware->web(append: [
\Illuminate\Session\Middleware\AuthenticateSession::class,
]);
Note that Laravel's session lifetime and the package's idle window are independent knobs; align them deliberately rather than letting one silently undercut the other.
Custom unlock validation. If your users authenticate via SSO, LDAP, or a passwordless flow, the default password check is wrong. Override the unlock form's authenticate/validation logic (subclass the plugin's page or override the view) and route it through your own guard — otherwise those users are permanently locked out.
Testing. Because the lock is middleware/page-level, cover both sides in Pest/Livewire tests: actingAs($user) → assert panel route redirects to the lock screen; then post the unlock form with the correct/incorrect password and assert the redirect target and that rate limiting triggers after N failures. For the idle path, travel time ($this->travel(6)->minutes() / Carbon::setTestNow()) rather than sleep(), and assert the lock does not fire one second before the threshold — that "premature locking" regression is exactly what 3.4.1 was fixing, so guard it in your suite.
A locked session is still a valid session. The cookie and server-side session remain live. Anyone with the browser can unlock if they know the password — and if the machine is unattended with a saved password manager, so can a passer-by. Treat lock screen as a shoulder-surfing deterrent, not an access control. For genuinely sensitive screens, add Filament's password confirmation (->passwordConfirmation()) on top.
Session lifetime won't save you by default. Users who click "lock" and walk away for an hour may still be unlockable because activity on the lock screen can keep the session warm. Explicitly expire the session, or store a locked-at timestamp and force a full logout past a hard limit.
Premature locking — upgrade to 3.4.1 if you saw this. 3.4.1 reworks session idle management and explicitly addresses premature locks. If you're on 3.4.x and worked around early locking (custom last_activity tracking, disabling idle, raising thresholds, patching middleware), remove those workarounds after upgrading or they'll fight the new behaviour. Conversely, if you rely on the old timing, re-verify your acceptance tests — a patch release can change timing semantics without a BC break.
Upgrading means re-checking the idle config. New releases can add or rename config keys. Copy your local values into the new published file rather than overwriting, and confirm the idle window actually applies (log out of a panel, wait past the threshold, see the lock screen). Config-cache gotcha: php artisan config:cache will happily serve stale idle values after an upgrade — clear it as part of deploy.
Rate limiting is optional and off in some configs. Brute-forcing the unlock form is the obvious attack. Turn on the built-in rate limit and confirm it's keyed per-user or per-session — a global limiter lets one attacker lock out everyone.
Multi-tab and multi-device behaviour. Lock state is per session. A second tab in the same browser usually follows along; a second device does not. Set expectations in your docs, and don't promise "remote lock".
Redirect target after unlock. Verify that unlocking returns the user to the URL they originally requested (url.intended), not always the dashboard. If it doesn't, capture the intended URL yourself before redirecting to the lock screen.
Asset and view caching. After composer update, run php artisan filament:upgrade && php artisan view:clear. Stale compiled Blade for the lock screen is the most common "my customisation didn't apply" report.
Publish views before styling. If you want to restyle the lock screen, publish the views (--tag="filament-lockscreen-views") rather than editing vendor/ — the package is small and updates will overwrite anything you patch in place.
Read the changelog, not just the version bump. 3.4.1's other two entries are Dependabot bumps to GitHub Actions (fetch-metadata, actions/checkout) — CI-only, zero runtime impact. The behavioural change is entirely PR #105. Useful heuristic for this package: skip the dependabot lines, read only the refactor:/feat:/fix: commits.
Small-package risk. 62 stars, zero dependents, and one maintainer. Pin an exact version in composer.json ("marjose123/filament-lockscreen": "3.4.1"), read the diff on upgrade (especially around the lock facade, config keys, and — as of 3.4.1 — idle timing), and keep your integration behind the thin wrapper class mentioned above so a breaking change is a one-file fix — or a fork.
Debugging a stuck lock. If a user reports being permanently locked out: check the session driver (database/redis sessions survive deploys, file sessions don't), check the published config's rate limit window, and confirm the trait is present on the model if the version requires it. php artisan route:list | grep -i lock will tell you quickly whether the plugin's routes actually registered. For unexpected locks, dump the session's last-activity/idle timestamp against the configured threshold before blaming the package — 3.4.1 changed that comparison.
How can I help you explore Laravel packages today?