Dynamic session cookie name in Laravel

Here’s a case that may be useful when authenticating multiple apps from a single Laravel code-base.

Recently I’ve built an application API and admin UI in the same Laravel code-base. This made lots of things easier to manage, including DB-related stuff. The API is consumed by a single-page app (ReactJS) and the backend is built with Laravel Orchid.

Also, it’s worth noting that the API is using Laravel Sanctum to provide session-based authentication to the front-end application.

The problem

Because both the front-end app (ReactJS+API) as well as the admin interface (Orchid) are using session-based authentication, and both are served from the same Laravel code-base, the session cookie name is the same in both cases, which means that sessions created in one app are automatically read by the other, which will obviously fail, because the apps have entirely different user accounts.

Trying to solve this thing proved to be more difficult than I thought, since Laravel is built from the ground up to use the configured session cookie name (i.e. session.cookie defined in config/session.php).

What makes things worse is that Laravel creates the session on the handled request before any middleware gets executed, and the created session already has the initially configured name set.

The solution

After several hours of trials, here’s what worked, in short:

  • Created a middleware called session.tenant that takes a string parameter (a tenant name).
  • In this middleware, I set the value of the config setting session.cookie but also the run-time session name (i.e. Session::setName()).
  • In Kernel.php I prepended the session.tenant:admin middleware to the admin UI routes (basically the web group) and the session.teant:api to the API routes (the api group).

It might not be the most elegant approach, but these changes proved to work very well with minimal code and without touching any of Laravel’s internals. Checkout the code below for details.

app/Http/Middleware/SessionTenant.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Session;

class SessionTenant
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next, string $tenant)
    {
        $original = Session::getName();
        $modified = "{$original}_{$tenant}";

        Session::setName($modified);
        Config::set('session.cookie', $modified);

        return $next($request);
    }
}

app/Http/Kernel.php

I’m only showing the relevant additions to the Kernel.php file, the rest is just the usual Laravel code.

# ...

protected $middlewareGroups = [
    'web' => [
        'session.tenant:platform',
        # ...
    ],
    'api' => [
        'session.tenant:api',
        # ...
    ],
];

# ...

protected $routeMiddleware = [
    # ...
    'session.tenant' => \App\Http\Middleware\SessionTenant::class,
];

public function __construct(Application $app, Router $router)
{
    parent::__construct($app, $router);
    $this->prependToMiddlewarePriority(\App\Http\Middleware\SessionTenant::class);
}

Note also the call to $this->prependToMiddlewarePriority(...) in the constructor (at the end). This pushes the middleware at the start of the processing chain so the session name is changed as soon as possible.

Hope it helps, enjoy!

One comment

Don't keep it to yourself!...