overtrue/laravel-stateless-session
Stateless session driver for Laravel that keeps session data in signed cookies instead of server-side storage. No Redis, database, or files required, so sessions work across servers and scale horizontally. A drop-in driver that preserves the familiar session API while removing shared session state.
This package gives Laravel API/SPA clients a cookie-less session: the client sends a session ID in a request header and Laravel returns the (possibly new) session ID in a response header. 3.0.0 is a maintenance-focused major: it drops Laravel ≤12 and PHP <8.3, fixes several header/session-ID edge cases, and adds real middleware test coverage.
Minimum steps:
UPGRADE.md in the repo, then install:
composer require overtrue/laravel-stateless-session:^3.0 --with-all-dependencies
bootstrap/app.php middleware stack — the 3.0 docs now explicitly cover Laravel 13 registration.session()->regenerate(), validation error bags) without cookie handling.Look first at the package's middleware and its config file to confirm the exact request/response header names in your install before wiring the client.
Client-side header retention (the core pattern). Store the returned session ID and send it back on the next request. 3.0.0 now returns the header on the first request too, including when the incoming header is missing, empty, undefined, or 0, so the client rule is simply: always overwrite your stored ID with the most recent returned value.
axios.interceptors.response.use(
(response) => {
const id = response.headers['x-session-id']; // use your configured name
if (id) store.set(id);
return response;
},
(error) => {
const id = error.response?.headers['x-session-id'];
if (id) store.set(id); // keep ID on 4xx/5xx, but don't swallow the error
return Promise.reject(error);
}
);
The response interceptor must reject, not resolve — the documented example deliberately retains IDs from rejected responses while re-throwing.
Keep the ID after regeneration. Login, logout and session()->invalidate() produce a new/final session ID; 3.0.0 returns the request's final session ID, so a single interceptor covers regeneration without extra round-trips or explicit "refresh session" endpoints.
CORS wiring. Because the ID lives in a custom header, browsers need explicit permission:
Access-Control-Expose-Headers: X-Session-Id so JS can read it;Access-Control-Allow-Headers so it can be sent;supports_credentials is not required for the header flow, but is if you mix in cookie auth.Middleware placement. Register the package middleware on the same group as StartSession (typically the api group or a dedicated stateless group). It needs to run around session start/finish to read the incoming ID and write the outgoing one.
Testing pattern. Mirror the package's own approach: feature tests through the real HTTP kernel asserting the header on first request, on subsequent requests, after regenerate(), after invalidate(), and on error responses. Use Testbench for package-level tests and file/database session drivers so persistence is actually exercised.
undefined/null into storage./session bootstrap endpoint instead.expose_headers/allow_headers in the same deploy.Cache-Control: no-store or Vary on the session header).$response->headers in a feature test, confirm middleware ordering relative to StartSession, confirm CORS exposure, and confirm your client interceptor is attached to the same Axios instance as the failing call.cghooks binary; if you had worked around that failure in CI, the workaround can be deleted.^3.0 will float across patch/minor releases with more header-semantics fixes; pin exactly if your client contract is sensitive, and keep --with-all-dependencies for the Laravel 13 transitive upgrades.How can I help you explore Laravel packages today?