laravel-dev-lock maintained by statikbe
Password protects environments that should not be publicly reachable — staging, acceptance, a client preview — without touching .htaccess, basic auth or your web server config.
The whole package is one middleware. Turn it on with an env variable, add it to the web middleware group, and every request has to pass a password form first. Health checks and API routes stay reachable, and so do whitelisted IPs.
[!NOTE] This hides an environment, it is not authentication. It keeps crawlers, search engines and forwarded links out of a staging site behind one shared password; it is not a substitute for protecting user data.
Requirements
| Supported | Covered by CI | |
|---|---|---|
| PHP | 8.3+ | 8.3, 8.4, 8.5 |
| Laravel | 12, 13 | 12, 13 |
Everything the package claims is proven on every push: the matrix is exactly its Composer
constraints, on Linux and Windows, in both prefer-lowest and prefer-stable lanes.
[!NOTE] Laravel 10 and 11 are deliberately out of scope. Both branches are past their security-support window and every release in them carries an advisory that will not be patched (CVE-2026-48019), so Composer refuses to install them without disabling its advisory policy. On those versions, upgrade the app rather than the lock.
Installation
Install the package via Composer:
composer require statikbe/laravel-dev-lock
Register the middleware on the web group in bootstrap/app.php:
use Illuminate\Foundation\Configuration\Middleware;
use Statikbe\DevLock\Http\Middleware\DevLockMiddleware;
->withMiddleware(function (Middleware $middleware): void {
$middleware->appendToGroup('web', DevLockMiddleware::class);
})
Apps that still route middleware through app/Http/Kernel.php — upgraded from Laravel 10 and never
moved to the slim skeleton — append it to the same group there instead:
// app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
// ... the rest of the group
\Statikbe\DevLock\Http\Middleware\DevLockMiddleware::class,
],
];
[!IMPORTANT] It has to be the
webgroup, not global middleware. The middleware identifies its own password routes with$request->routeIs(), and global middleware runs before the router resolves a route — registered withappend()(or in$middleware/$middlewareGroupsoutsideweb) instead ofappendToGroup()the password page redirects to itself instead of rendering the form.
Then set the environment variables on the environments you want locked:
DEV_LOCK_ENABLED=true
DEV_LOCK_PASSWORD="a long password you can share with the client"
That is all. The lock is off by default, so nothing changes on environments that do not set DEV_LOCK_ENABLED.
Configuration
| Env variable | Config key | Default | Description |
|---|---|---|---|
DEV_LOCK_ENABLED |
dev_enabled |
false |
Switches the lock on. While it is off, requests pass through and the password routes are not even registered. |
DEV_LOCK_PASSWORD |
dev_password |
null |
The password visitors have to enter. There is no default on purpose — see below. |
DEV_LOCK_WHITELIST_IPS |
dev_whitelist_ips |
['127.0.0.1', 'localhost'] |
IPs that never see the form. An array, or a comma separated string: DEV_LOCK_WHITELIST_IPS="1.2.3.4,5.6.7.8". |
| — | dev_skip_patterns |
['up', 'api/*'] |
Paths that stay reachable while the lock is on. Matched with Request::is(), so wildcards work. |
DEV_LOCK_VITE_ENTRYPOINT |
dev_vite_entrypoint |
'resources/css/app.css' |
The compiled CSS used to style the password page. Point it at the entrypoint your app builds, or set it to null to always use the package's own stylesheet. |
Config keys live under the dev-lock namespace, so dev_enabled is
config('dev-lock.dev_enabled').
[!WARNING] Enabling the lock without setting
DEV_LOCK_PASSWORDfails closed: every protected request gets a503telling you the password is missing. It never renders a form nobody can get past, and it never lets requests through.
Publish the config file to change the skipped paths:
php artisan vendor:publish --tag="dev-lock-config"
How it works
While the lock is on, every request through the web group is checked in this order:
- Paths matching
dev_skip_patternspass through — theuphealth check andapi/*by default. - Sessions that already entered the password pass through.
- Whitelisted IPs pass through.
- Everything else is redirected to
/__dev-lock, which answers401with the password form. The original URL travels along in?redirect_to=, so visitors land where they were going after unlocking.
A few details worth knowing:
- Failed attempts are rate limited per IP: 5 tries, then a 5 minute lockout.
- The session ID is regenerated on success, so a planted session cannot be promoted to an unlocked one.
?redirect_to=only acceptshttp/httpsURLs on your own host; anything else lands on/. The lock cannot be used as an open redirect.- The form is served with
X-Frame-Options: DENY,X-Robots-Tag: noindex, nofollowand ano-storeCache-Control.
The password page
The page is styled with Tailwind utility classes and your app's compiled CSS. With Tailwind v4 the class scanner has to see the package's Blade file, otherwise the page renders unstyled. Add it as a source in your CSS entrypoint:
/* resources/css/app.css */
@source '../../vendor/statikbe/laravel-dev-lock/resources/views';
Building a different entrypoint? Point the config at it instead of publishing anything:
DEV_LOCK_VITE_ENTRYPOINT="resources/css/site.css"
If your app has no Vite build, the page still works. The lock gates every request, so a page that cannot render would leave you with no way into the environment. Rather than calling @vite in the view, the middleware resolves your compiled CSS and inlines the package's own resources/css/dev-lock.css when it cannot — which covers an app that does not use Vite at all, a deploy where the frontend build never ran, and an entrypoint that is not in the manifest. The fallback page is plain but fully usable, and nothing needs publishing to get it. Set DEV_LOCK_VITE_ENTRYPOINT=null to skip Vite entirely and always use it.
To restyle the fallback, publish the stylesheet and edit it — the middleware prefers your copy over the packaged one:
php artisan vendor:publish --tag="dev-lock-css"
Prefer your own markup? Publish the view and edit it:
php artisan vendor:publish --tag="dev-lock-views"
If you do, keep the dev-lock-card, dev-access-heading and dev-lock-note ids — they are the only hooks the fallback stylesheet has.
The page ships with English and Dutch translations. Publish them to change the wording:
php artisan vendor:publish --tag="dev-lock-lang"
Or publish everything at once:
php artisan vendor:publish --tag="dev-lock"
Replacing your own dev lock
Plenty of apps already carry a copy of this lock in their own namespace, usually as
app/Http/Middleware/DevLockMiddleware.php with a resources/views/dev-lock.blade.php and a
pair of dev.lock routes in routes/web.php. Find them before registering the package one — the
two classes are indistinguishable at a glance in bootstrap/app.php:
grep -rn "dev_lock_authenticated\|dev\.lock\|DevLock" app bootstrap config routes resources tests
Publish this package's config before you delete the app's own, so the existing settings have somewhere to go:
php artisan vendor:publish --tag="dev-lock-config"
Then move each setting across. All but one key is env driven, so most of this is .env work — but
dev_skip_patterns has no env variable, which makes the published config the only place to keep the
paths your old lock exempted:
| Your old setting | Moves to | Where |
|---|---|---|
| on/off flag | dev_enabled |
DEV_LOCK_ENABLED |
| the password | dev_password |
DEV_LOCK_PASSWORD |
| allowed / whitelisted IPs | dev_whitelist_ips |
DEV_LOCK_WHITELIST_IPS, or the config array |
| excluded / skipped paths | dev_skip_patterns |
the published config only |
An app that also exempted a webhook or callback URL drops back to the ['up', 'api/*'] default
without an error, so carry those over explicitly:
// config/dev-lock.php
'dev_skip_patterns' => [
'up',
'api/*',
'webhooks/*', // ported from your own lock
],
Now delete the app copies, point the middleware registration at
Statikbe\DevLock\Http\Middleware\DevLockMiddleware, rename the old env variables to
DEV_LOCK_* in .env, .env.example and your deploy templates, and run
php artisan optimize:clear so cached routes and config stop resurrecting the deleted class.
Three things to watch:
resources/views/dev-lock.blade.phpis not this package's published view — publishing writes toresources/views/vendor/dev-lock/, so a file directly underresources/viewsis the app's own and can go.- App-local
/__dev-lockroutes collide with the package's on both URI and name, and which one answers depends on registration order. Remove them rather than reason about the order. - Once you have ported your values, do not re-run
vendor:publishwith--forcefor the config tag — it overwritesconfig/dev-lock.phpwith the package defaults and takes the ported skip patterns with it. And keep the password in the environment, never in the published file.
The bundled Boost skill walks an agent through exactly this — see below.
AI agents (Laravel Boost)
The package ships a Laravel Boost skill,
dev-lock-development, at resources/boost/skills/. It teaches an agent how to adopt the
package: registering the middleware on the web group, the env variables, the publish tags, the
Tailwind @source line, and replacing an app's own dev lock.
Boost finds it automatically in any app that has this package installed. To enable it:
composer require laravel/boost --dev
php artisan boost:install
Pick your agents when prompted, and select statikbe/laravel-dev-lock at "Which third-party AI
guidelines/skills would you like to install?". To refresh only the skills on a project that
already uses Boost:
php artisan boost:install --skills
Boost copies the skill to .ai/skills/dev-lock-development and links it into each agent's
skills directory — .claude/skills/ for Claude Code, and the equivalent for Codex, Cursor, Copilot,
Gemini, Junie, Amp and OpenCode. Commit those files so the whole team's agents pick the skill up.
Not interested? Exclude it by name in the app's config/boost.php:
'skills' => [
'exclude' => ['dev-lock-development'],
],
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Thank you for considering contributing to Laravel Dev Lock! Please review our contributing guide to get started.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
Laravel Dev Lock is open-sourced software licensed under the MIT license.