sobstel/sesshin
Sesshin provides encrypted, authenticated session storage for PHP. It transparently encrypts session data with libsodium or OpenSSL, keeping contents unreadable in files, Redis, or databases while staying compatible with PHP's native session handling.
Sesshin is a framework-agnostic, security-focused replacement for native PHP sessions. It wraps the pieces you usually hand-roll (ID generation, cookie handling, fingerprinting, encryption) into composable objects. It is not a Laravel package, so day-to-day use means wiring it into your own bootstrap or a small service provider.
1. Install
composer require sobstel/sesshin
2. Where to look first
src/Sesshin/Session.php — the façade everything else plugs into.src/Sesshin/Store/ — where session data lives (Native, Memory).src/Sesshin/Id/Generator/ and src/Sesshin/Id/Handler/ — how IDs are created and transported.src/Sesshin/Fingerprint/ — hijack detection (checkers + storage).src/Sesshin/Crypt/ — encryption at rest.Read Session.php plus one implementation of each interface before anything else; the rest of the library is small.
3. Minimal bootstrap
use Sesshin\Session;
use Sesshin\Store\Native;
use Sesshin\Id\Generator\Random;
use Sesshin\Id\Handler\Cookie;
$session = new Session(
new Native(),
new Random(), // CSPRNG-backed id generator
new Cookie([ // id transport
'path' => '/',
'httponly' => true,
'secure' => true,
'samesite' => 'Lax',
])
);
$session->start();
4. First use case: login state
// login.php
$session->regenerateId(); // kill session-fixation from pre-auth
$session->set('user_id', $user->id);
$session->set('logged_in_at', time());
// later requests
if ($session->get('user_id') === null) {
// unauthenticated
}
That’s the 80% path: encrypted, regenerated-on-login session state with a hardened cookie. Add fingerprinting and encryption in the next step.
Wrap it in one app-level service. Don’t scatter new Sesshin\Session(...) across controllers. Build a single configured instance (or a container singleton) so cookie options, fingerprint rules, and the crypt key are defined in one place.
final class AppSession
{
private Session $session;
public function __construct(array $config)
{
$this->session = new Session(
new Native(),
new Random(),
new Cookie($config['cookie'])
);
$this->session->setFingerprint(new Fingerprint([
new UserAgent(),
new Ip(), // configure trusted proxies if behind a load balancer
]));
$this->session->setCrypt(new OpenSSL($config['key']));
$this->session->start();
}
public function get(string $k, $default = null) { return $this->session->get($k, $default); }
public function set(string $k, $v): void { $this->session->set($k, $v); }
public function regenerate(): void { $this->session->regenerateId(); }
public function destroy(): void { $this->session->destroy(); }
}
Auth flow pattern. Regenerate on privilege change only — login, logout, password change, role escalation. Regenerating on every request breaks concurrent tabs and mobile clients.
// on successful login
$session->regenerateId();
$session->set('auth', ['id' => $user->id, 'role' => $user->role]);
// on logout
$session->destroy();
Namespaced keys instead of nested blobs. Sesshin’s accessor API is flat (get/set/has/remove). Prefer explicit prefixes (auth.user_id, cart.items, flash.success) over stuffing arrays into one key — it makes clearing one concern and grepping logs easier.
Flash messages are just a convention on top of the store:
$session->set('flash.success', 'Saved.');
// next request, after rendering:
$session->remove('flash.success');
Encryption for sensitive payloads only. Encryption adds CPU and key-management overhead. Use setCrypt() when the store is disk/DB-backed and holds PII or tokens; skip it if the store is already an encrypted at-rest backend (e.g., Redis with TLS + disk encryption) and the cookie is secure + httponly. Rotate keys by decrypting with the old key, re-encrypting, and invalidating sessions.
Testing with the memory store. Swap Native for Sesshin\Store\Memory in unit tests; no headers, no session_start, deterministic.
$session = new Session(new Memory(), new Random(), new Cookie());
$session->start();
Framework integration. There is no first-party Laravel/Symfony bridge; treat it as a library, not a driver. In Laravel, instantiate it in a singleton service provider and keep it out of Laravel’s own session middleware lifecycle — do not try to make it a SessionHandlerInterface under the existing driver unless you are prepared to disable Laravel’s session middleware. In legacy or Slim projects, resolve it at the front controller, before any output.
mcrypt was removed; use the OpenSSL crypt implementation, not the legacy one.start() has to run before any output. In CLI this is irrelevant, but a stray echo or ?> at the top of a bootstrap file will silently break session persistence.session_start() (or Laravel’s session middleware runs), the native store will conflict and you may lose or corrupt state. Pick one owner of the session lifecycle per request.User-Agent or raw IP will log out legitimate users on browser updates, mobile network switches, and proxy IP rotation. Either relax the checker (UA family, not exact string) or configure trusted proxies so IP resolution is stable.regenerateId() deletes the old record; concurrent in-flight requests holding the old ID will appear logged out. Do it on login/logout only, not per-request.secure over HTTP), fingerprint mismatch, or old ID after regeneration.Store for Redis/DB-backed storage,Checker for custom fingerprint dimensions (e.g., bind to an org/tenant ID, not to client IP),Generator for ID format policies,Handler for non-cookie transport (rare).
Keep custom checkers additive; removing one later invalidates every existing session and forces re-authentication.$_SESSION. Treat the Sesshin store as the source of truth. Reading/writing $_SESSION directly alongside it is the fastest way to produce state you can’t explain during an incident.Auth::user(), no CSRF helper), your AppSession wrapper is where you define lifetime, cookie attributes, fingerprint rules, and logout semantics. That class is the thing you actually maintain.How can I help you explore Laravel packages today?