The Problem: Two Apps, One User, Zero Shared Sessions
Incremental migrations to Laravel are practical, but they create an awkward authentication gap. A user logs in on the old CodeIgniter or custom PHP side, follows a link to a Laravel-handled route, and Laravel — knowing nothing about that session — redirects them to a login form. Laravel Legacy Bridge, a package by Chris Keller, closes that gap without requiring users to authenticate twice.
The package reads the legacy session cookie on unauthenticated requests, decodes the session payload from the legacy database, resolves a user ID, and calls loginUsingId(). Laravel then writes its own session, and every subsequent request bypasses the legacy store entirely.
How the Bridge Works
Registering one middleware is all it takes to put the bridge in the request path:
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\Chr15k\LegacyBridge\Http\Middleware\LegacySessionBridge::class,
]);
})
The middleware only runs on unauthenticated requests. Once Laravel has established its own session, the legacy store is never consulted again. The service provider also automatically excludes the legacy cookie from Laravel's EncryptCookies middleware, so there is no list to maintain manually.
Resolvers and Payload Formats
Legacy apps store the user ID under wildly different keys. The package handles this through configurable resolver drivers in config/legacy-bridge.php:
// Auto-detection (default)
'resolver' => ['driver' => 'auto'],
// Explicit dot-notation key
'resolver' => ['driver' => 'key', 'key' => 'user_id'],
// Custom class for complex mappings
'resolver' => ['driver' => 'custom', 'class' => \App\Bridge\LegacyUserResolver::class],
The README recommends starting with auto and switching to key or custom before going to production. A custom resolver is also the right place to map old user IDs to new ones if your migration re-seeded the users table.
Payload format is a separate setting that accepts auto, php_session, json, laravel, or encrypted. The encrypted format reads the legacy app's key from LEGACY_BRIDGE_APP_KEY.
Events Instead of Log Noise
The bridge writes nothing to your log files. Instead it dispatches three typed events:
LegacySessionBridged— successful authenticationLegacySessionBridgeFailed— known failure with aBridgeFailureReasonenum (eight cases includingMissingCookie,SessionExpired, andUserNotResolved)LegacySessionBridgeError— unexpected exception
Failure events carry a BridgeContext DTO with everything the bridge resolved before stopping: cookie name, session ID, decoded payload, resolved user ID, and basic request context (IP, path, method, user agent).
Installation and the Verify Command
composer require chr15k/laravel-legacy-bridge
php artisan legacy-bridge:install
The interactive install command includes presets for common legacy frameworks, collects database credentials, and writes the required .env entries.
Before real traffic hits the bridge, run the verify command against your actual legacy database:
php artisan legacy-bridge:verify
php artisan legacy-bridge:verify --session-id=a_real_session_id
Without a session ID it checks configuration, database connectivity, table existence, resolver setup, and cookie name collisions. With a real session ID it reports exactly what the bridge would do: format detected, payload keys found, user ID resolved, user confirmed to exist. It authenticates no one and modifies nothing.
Security Considerations
Read the security section of the README before deploying. Key points:
- The bridge deserializes payloads directly from the legacy sessions table — use read-only database credentials where possible.
- The legacy cookie travels unencrypted by design; both applications must be served over HTTPS.
- The default
after_writeinvalidation strategy deletes the legacy session once Laravel writes its own. Setting invalidation toneverin production is explicitly discouraged. - The first release supports database sessions only (not file, Redis, or Memcached), web requests only, and the default auth guard only.
- Requires Laravel 13 and PHP 8.3 or newer.
Key Takeaways
- One middleware registration bridges authenticated sessions from any legacy PHP app into Laravel.
- Supports PHP session encoding, JSON, Laravel's serialized format, and encrypted payloads.
- Three resolver drivers handle simple to complex user ID lookups, including ID remapping.
- Typed events give you full observability without polluting log files.
- The
legacy-bridge:verifycommand lets you test the full pipeline against real data before deployment. - Legacy sessions are invalidated after a successful bridge by default, preventing replay.