cakephpcookiescakephp-2.3samesitecakephp-2.x

How can I set the samesite cookie attribute in CakePHP 2.3?


CakePHP 2.3 sets the Session variables (including cookie attributes) in the core.php file. I need to set samesite=None and Secure=true for the session cookie, but it doesn't appear to have those settings available in the configuration, which shows only the following options:

This is how I have it now:

Configure::write('Session', array(
                                    'defaults' => 'database',
                                    'handler' => array('model' => 'cake_sessions'),
                                    'timeout' => 60
                                    ));

Is there a workaround for this? I've been looking at how to do this with php but I'm not sure how I would edit the session cookie that CakePHP creates with the attributes I want, or if that is possible at all once the cookie has been created.


Solution

  • Before PHP 7.3

    In PHP versions earlier than PHP 7.3, you can inject the SameSite attribute by utilizing the cookie path hack, which consists of appending further cookie attributes to the path, by simply closing the path of with a semicolon.

    Simply configure the session.cookie_path ini option in app/Config/core.php accordingly, for example like this in case your application's base path is /:

    Configure::write('Session', [
        'defaults' => 'php',
        'ini' => [
            'session.cookie_path' => '/; SameSite=None',
        ],
    ]);
    

    The Secure attribute (ie the session.cookie_secure ini option) will automatically be configured by CakePHP when you're visiting your site via https.

    As of PHP 7.3

    In PHP versions as of PHP 7.3 you would use the session.cookie_samesite ini option instead:

    Configure::write('Session', [
        'defaults' => 'php',
        'ini' => [
            'session.cookie_samesite' => 'None',
        ],
    ]);
    

    Other cookies

    All of this of course only applies to session cookies, if you're using additional cookies via the Cookie component, then you'd have to utilize the path hack there too, by modifying the $path property accordingly, and unlike with sessions, you'd have to explicitly enable secure cookies:

    $this->Cookie->path = '/; SameSite=None';
    $this->Cookie->secure = true;
    

    With PHP 7.3+ you'd have to use a custom/extended cookie component, and an extended/custom response class where you'd override the CookieComponent::_write(), CakeResponse::cookie() and CakeResponse::_setCookies() methods accordingly, so that the component allows to set an option for same site, and the response will pass it over to the setcookie() call.

    Example:

    <?php
    // in app/Controller/Component/AppCookieComponent.php
    
    App::uses('CookieComponent', 'Controller/Component');
    
    class AppCookieComponent extends CookieComponent
    {
        public $sameSite = 'Lax';
    
        protected function _write($name, $value)
        {
            $this->_response->cookie(array(
                'name' => $this->name . $name,
                'value' => $this->_encrypt($value),
                'expire' => $this->_expires,
                'path' => $this->path,
                'domain' => $this->domain,
                'secure' => $this->secure,
                'httpOnly' => $this->httpOnly,
                'sameSite' => $this->sameSite,
            ));
    
            if (!empty($this->_reset)) {
                $this->_expires = $this->_reset;
                $this->_reset = null;
            }
        }
    }
    
    <?php
    // in app/Network/AppResponse.php
    
    App::uses('CakeResponse', 'Network');
    
    class AppResponse extends CakeResponse
    {
        public function cookie($options = null)
        {
            $options += [
                'sameSite' => 'Lax',
            ];
    
            return parent::cookie($options);
        }
    
        protected function _setCookies()
        {
            foreach ($this->_cookies as $name => $cookie) {
                $options = [
                    'expires' => $cookie['expire'],
                    'path' => $cookie['path'],
                    'domain' => $cookie['domain'],
                    'secure' => $cookie['secure'],
                    'httponly' => $cookie['httpOnly'],
                    'samesite' => $cookie['sameSite'],
                ];
                setcookie($name, $cookie['value'], $options);
            }
        }
    }
    

    Inject the custom response in the front controller:

    // in app/webroot/index.php
    
    App::uses('Network', 'AppResponse');
    
    $Dispatcher = new Dispatcher();
    $Dispatcher->dispatch(
        new CakeRequest(),
        new AppResponse()
    );
    

    Alias the Cookie component with the custom component class:

    // in app/Controller/AppController.php
    
    public $components = [
        'Cookie' => [
            'className' => 'AppCookie',
        ],
    ];
    

    and then configure the component accordingly before using it:

    $this->Cookie->sameSite = 'None';
    $this->Cookie->secure = true;
    

    or use the response object directly to set your cookies:

    $this->response->cookie([
        'name' => 'cookie name',
        'value' => 'cookie value',
        'expire' => time() + (60 * 24),
        'path' => '/',
        'domain' => '',
        'secure' => true,
        'httpOnly' => false,
        'sameSite' => 'None',
    ]);