rairlie/laravel-locking-session
Laravel package for locking user sessions to a single device or browser, preventing concurrent logins and duplicate active sessions. It helps secure accounts by enforcing one active session per user with configurable middleware and session handling.
composer require rairlie/laravel-locking-session
php artisan vendor:publish --provider="Rairlie\LockingSession\LockingSessionServiceProvider"
The package auto-discovers its service provider. Your first three moves:
StartSession. You can read it all in ten minutes, and you should, because the config surface is small and the behavior is entirely in those two places.config/locking-session.php) and confirm the exact key names for the lock store, wait timeout, and lock TTL. Those three knobs are the entire operational story.web group, not globally.The canonical first use case: a checkout or wizard page that fires four AJAX requests on load, each of which writes to the session (cart row, step counter, flash message, CSRF-adjacent state). Without locking, the last response to finish wins and you silently lose writes. With this middleware installed, those four requests queue behind each other and the session stays coherent.
Validate it immediately: open the app, hit a page with parallel XHRs, and watch the network waterfall. Sequential response completion on the same session is the signal it's working.
Scope the middleware narrowly first. Enable it for your authenticated app routes, not web globally, until you've measured the latency cost. Anonymous traffic — marketing pages, bots, health checks — gains nothing from serialization and will queue up behind itself.
Separate read-only traffic. The main production pattern is to keep exclusive locks for state-mutating routes and let GET polling endpoints (/notifications/count, /api/feed) bypass the middleware entirely. If the package exposes a shared/read lock mode, use it for those; otherwise route-exclude them. Otherwise a 2-second poller will serialize your whole UI behind itself.
Acquire late, release early. Anything slow that doesn't need the session — S3 uploads, third-party API calls, PDF generation — should happen outside the locked window. If you can't restructure, at minimum call session()->save() and release before the slow block, so the next request isn't waiting on your HTTP client.
Frontend coalescing still matters. Session locking fixes correctness; it doesn't fix the fact that you're firing four requests. Debounce and batch anyway — locking turns a data-corruption bug into a latency bug, and you'd rather not have the latency bug.
Test with real concurrency. Http::fake() and single-process feature tests will never catch this. Write a test that opens two parallel curl handles against the same session cookie, has both increment a session counter, and asserts the value is 2. Run it with and without the middleware to prove the middleware is doing anything.
Octane and long-running workers. If you run Laravel Octane, verify the lock is released in the middleware's termination path, not just on normal response. A leaked lock under Octane is a sticky 500 for one user until TTL expiry.
fastcgi_read_timeout (or your ALB idle timeout) produces a 504 instead of a clean error, and the request that did hold the lock keeps running invisibly. Set the wait timeout to something like 3–5 seconds and let it fail loudly.Http:: calls to your own hostname.composer.json constraints against your Laravel version and run your suite on the target version before committing. If the package doesn't support your Laravel release, the fallback is a ~40-line middleware around Cache::lock("session:{$id}")->block(...) — the concept is trivial, and this package's value is mostly that it's already wired correctly.How can I help you explore Laravel packages today?