fidum/padlock
Fidum Padlock is a Laravel package that adds simple password protection to your app or selected routes. Configure a password, apply the middleware, and keep staging sites, admin areas, or private pages locked behind a lightweight session-based lock.
Padlock puts a password gate in front of your Laravel app (or parts of it): a middleware checks whether the current session has been "unlocked" and, if not, redirects the visitor to a form where they enter a shared password. It's the package you reach for when you want a cheap, dependency-free way to keep staging sites, internal tools, demos, or pre-launch apps away from the public internet without building a user system.
Version 4.0.0 is a framework-alignment release, not a feature release. The only changes are:
That means the upgrade is driven entirely by your application's framework version, not by the package's API.
Minimal steps
composer show laravel/framework) and PHP 8.2+. If you're still on Laravel 11, stay on Padlock 3.x until the app is upgraded.composer require fidum/padlock:^4.0php artisan vendor:publish --tag=padlock-config (verify the tag in the README — tag names are the one thing that changes without a major bump)..env (e.g. PADLOCK_PASSWORD=...) — never in the config file — and make sure it exists before you run php artisan config:cache.Where to look first
config/padlock.php — the env key name, session key, redirect target, and any excluded paths all live here.src/Middleware/...) — read it before debugging; it's small and tells you exactly which session key gates access.routes/ in the package — the unlock form routes and their names, so you can exclude them from your own auth middleware.First use case
Protecting a whole staging environment in one line: append the middleware to the web group (or to your top-level route group) behind an environment check, so production is never gated.
Scope the lock with route groups, not globally
Gating everything globally is tempting, but it makes health checks, webhooks, and asset routes behave oddly. Prefer explicit groups:
Route::middleware(['web', 'padlock'])->group(function () {
Route::get('/', HomeController::class);
Route::get('/dashboard', DashboardController::class);
});
// Webhooks / API left untouched
Route::post('/stripe/webhook', WebhookController::class);
If you do want app-wide protection, wrap the registration in if (app()->environment('staging', 'local')) so the middleware never reaches production config.
Keep the password out of config and VCS
The config file should only read env('PADLOCK_PASSWORD'). Per-environment passwords via your host's secret store (Forge/Vapor/Doppler) mean staging and demo environments can have different passwords, and you can rotate one without touching another.
Layer it under real auth, not over it
Padlock answers "can this person see the app at all?", not "who is this person?". Keep it outside your auth middleware so the lock screen appears before login. On unlock it should redirect back to the originally requested URL, so deep links survive the gate.
Testing patterns
Because the gate is session-based, feature tests need the session primed or the middleware removed:
// Preferred for feature tests that aren't about the lock itself
$this->withoutMiddleware(\Fidum\Padlock\...\Middleware::class);
// Or prime the session with the unlocked flag
$this->withSession([config('padlock.session_key') => true])
->get('/dashboard')
->assertOk();
Write two or three dedicated tests for the lock itself — locked request redirects, wrong password re-renders with an error, correct password stores the session key — and then withoutMiddleware everywhere else. Your test suite should not have to know the password.
CI and local dev
Add PADLOCK_PASSWORD to .env.example and to your CI env so php artisan config:cache and browser tests don't fail on a missing value. Locally many teams set it to something trivial and simply keep it unlocked; the point is parity of config shape, not secrecy in dev.
Laravel Zero / console apps
4.0.0 tracks Laravel Zero 12.x as well, so the same lock primitive can be exercised in console-only apps. If you ship a Laravel Zero tool alongside a Laravel app, keep both on the same major of Padlock so the Illuminate constraints resolve cleanly.
The 3.x → 4.x upgrade is not optional if you upgrade Laravel
Laravel 12 upgrades pull illuminate/* to ^12, and Padlock 3.x is constrained to 11. If you upgrade the framework first, composer update will either fail to resolve or silently leave the package behind. Order of operations: upgrade the app, then bump Padlock in the same PR, and run the lock tests again.
Config caching will bite you
Any env() call only works before php artisan config:cache. If you deploy with config:cache in your pipeline (you should), the env var must exist at cache time — setting it afterwards has no effect until you re-cache. This is the number-one cause of "the password is right but it still rejects me" on Forge/Vapor/Envoyer.
Empty string is not null
PADLOCK_PASSWORD= (blank) versus the key being absent are handled differently by most packages of this shape. A blank password can mean "accept anything" or "lock everyone out" depending on the implementation — read the middleware's comparison before you leave it blank in any shared environment. Treat a missing value as a deploy failure, not a default.
Session driver matters
The unlock state lives in the session. That means:
SESSION_DRIVER=array in tests means the unlock never persists between requests — use withSession().web group (no session, no cookies) will never stay unlocked — APIs, webhooks, and signed-URL endpoints should be excluded.Redirect loops
If the lock screen route itself is inside the protected group, you get an infinite redirect. The package's own routes must be excluded — but if you moved/renamed them or added a catch-all route, re-check. Debug by hitting the unlock path directly with php artisan route:list --name=padlock and confirming the middleware list for each route.
Middleware priority
Padlock placed before StartSession in the global stack sees no session and will always redirect. Register it in the web group or in a route group where session middleware is already present; don't hand-roll the ordering in bootstrap/app.php unless you also reproduce the session middleware.
Extension points worth knowing
->middleware('throttle:5,1')) to stop password guessing.When not to use it
Padlock is a shared secret, not an auth system: no per-user auditing, no revocation per person, no password reset. If two people need different access or you need to know who saw what, promote the environment to real authentication (Fortify, Breeze, or a basic auth gate) instead of stretching Padlock.
How can I help you explore Laravel packages today?