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

Padlock Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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:

  • Supports Laravel 12.x (and Laravel Zero 12.x).
  • Drops Laravel 11.x support — this is the breaking change.

That means the upgrade is driven entirely by your application's framework version, not by the package's API.

Minimal steps

  1. Confirm your app is on Laravel 12 (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.
  2. composer require fidum/padlock:^4.0
  3. Publish the config: php artisan vendor:publish --tag=padlock-config (verify the tag in the README — tag names are the one thing that changes without a major bump).
  4. Set the password in .env (e.g. PADLOCK_PASSWORD=...) — never in the config file — and make sure it exists before you run php artisan config:cache.
  5. Register the middleware on the routes you want to protect (see below).
  6. Smoke-test in a private window: hit a protected URL, get the form, submit, confirm the session sticks and logging out / clearing cookies re-locks.

Where to look first

  • config/padlock.php — the env key name, session key, redirect target, and any excluded paths all live here.
  • The middleware class itself (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.

Implementation Patterns

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.

Gotchas and Tips

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().
  • Routes outside the web group (no session, no cookies) will never stay unlocked — APIs, webhooks, and signed-URL endpoints should be excluded.
  • Behind a load balancer without sticky sessions or a shared session store (Redis/DB), users get re-prompted randomly. This looks like a Padlock bug but is a session-infrastructure bug.

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

  • Publish and edit the unlock view to match your brand — a default unstyled form on a client demo is a bad first impression.
  • Add throttling to the unlock POST route (->middleware('throttle:5,1')) to stop password guessing.
  • Add a "lock now" action that forgets the session key, which is handy for shared demo devices.

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.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource