laravelsession-cookieslaravel-octane

How can I dynamically change the session cookie domain in Laravel Octane?


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?


Solution

  • 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);
        }
    }