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 thesession.tenant:admin
middleware to the admin UI routes (basically theweb
group) and thesession.teant:api
to the API routes (theapi
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