My application can be accessed from several different domains, and requires cookies to be accessible from multiple subdomains (for example, app.example.com
also uses abc.example.com
and app.example.net
also uses abc.example.net
).
I wrote the following middleware to accomplish this:
namespace App\Http\Middleware;
use App\Models\Product;
use Closure;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Cookie\CookieJar;
class ChooseProduct
{
public function __construct(
private readonly Repository $config,
private readonly CookieJar $cookie
)
{}
public function handle($request, Closure $next, $group)
{
// Strips off the port, if present
$requestDomain = strtok($request->server('HTTP_HOST'), ':');
// Make sure the domain is one of our known products, to prevent
// someone from pointing a different domain name to this server.
// This just looks in the database to find the "main" domain for the product.
$product = Product::getForDomain($requestDomain);
if ($product) {
$cookieConfig = $this->config->get('session');
$this->cookie->setDefaultPathAndDomain($cookieConfig['path'], ".{$product->main_domain}", $cookieConfig['same_site']);
$this->config->set('session.domain', ".{$product->main_domain}");
}
return $next($request);
}
}
This works perfectly when running using artisan serve
for development and with php-fpm or Nginx Unit for production. (Using the example domains above, a request to app.example.com
will return cookies with the domain set to .example.com
, while a request to app.example.next
will return cookies with the domain set to .example.net
.
When using Laravel Octane (with FrankenPHP), the domain is not present in the Set-Cookie
header for the session cookie. However, the correct domain is present in the Set-Cookie
header for the XSRF-TOKEN
cookie.
I looked in the Laravel Framework source, and I see that the StartSession
middleware gets its configuration using $this->manager->getSessionConfig()
(where $this->manager
is an Illuminate\Sessopm\SessionManager
object, while the VerifyCsrfToken
middleware gets its configuration using config('session')
.
How can I get this to work for all cookies?
The problem is that the SessionManager
uses it's own copy of config
, which is not the same as the rest of the application.
Two Pull Requests (one on Octane, the other on Laravel itself) were made in March 2023 to address exactly this issue.
For some reason, in April 2023, they removed the code again (Octane, Framework).
I found a workaround, which is ugly, but functional:
class ChooseProduct
{
public function __construct(
private readonly Repository $config,
private readonly CookieJar $cookie,
// ADDED: Get a reference to the session manager
private readonly SessionManager $sessionManager,
)
{}
public function handle($request, Closure $next, $group)
{
// Strips off the port, if present
$requestDomain = strtok($request->server('HTTP_HOST'), ':');
// Make sure the domain is one of our known products, to prevent
// someone from pointing a different domain name to this server.
// This just looks in the database to find the "main" domain for the product.
$product = Product::getForDomain($requestDomain);
if ($product) {
$cookieConfig = $this->config->get('session');
$this->cookie->setDefaultPathAndDomain($cookieConfig['path'], ".{$product->main_domain}", $cookieConfig['same_site']);
$this->config->set('session.domain', ".{$product->main_domain}");
// ADDED: modify the configuration of the session manager
$this->sessionManager->getContainer()->make('config')->set('session.domain', ".{$product->main_domain}");
}
return $next($request);
}
}