Laravel Legacy Bridge: Carry Authenticated Sessions from a Legacy PHP App into Laravel
Laravel Composer Pacakge #Laravel #PHP #Session Management #Migration #Laravel Packages

Laravel Legacy Bridge: Carry Authenticated Sessions from a Legacy PHP App into Laravel

4 min read Mohamed Said Mohamed Said

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 authentication
  • LegacySessionBridgeFailed — known failure with a BridgeFailureReason enum (eight cases including MissingCookie, SessionExpired, and UserNotResolved)
  • 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_write invalidation strategy deletes the legacy session once Laravel writes its own. Setting invalidation to never in 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:verify command lets you test the full pipeline against real data before deployment.
  • Legacy sessions are invalidated after a successful bridge by default, preventing replay.

Source: Laravel Legacy Bridge on Laravel News

Found this useful?

Frequently Asked Questions

3 questions
Q01 Does Laravel Legacy Bridge work with Redis or file-based legacy sessions?
No. The first release supports database-backed sessions only. File, Redis, and Memcached session drivers are not supported yet.
Q02 How do I handle legacy apps that store user IDs under a non-standard key?
Set the resolver driver to 'key' with a dot-notation path, or implement a custom resolver class. A custom resolver also lets you remap old user IDs to new ones if your migration re-seeded the users table.
Q03 Is it safe to deserialize payloads from the legacy sessions table?
The legacy sessions table becomes a trust boundary because payloads are deserialized directly. The package author recommends using read-only database credentials for the legacy connection and ensuring both applications are served over HTTPS.

Continue reading

More Articles

View all