Codeigniter: [Suggestion] same-site attribute for cookie

Created on 30 Jun 2019  路  20Comments  路  Source: bcit-ci/CodeIgniter

Let me suggest to support same-site attribute through $config['cookie_samesite']

Ref: https://www.owasp.org/index.php/SameSite

Enhancement Feature Request

Most helpful comment

@otengkwame First code is for the application/config/config.php file. This lines:
https://github.com/bcit-ci/CodeIgniter/blob/0648ae2acb3fae84f2cc49e15edb345ccef6cad5/application/config/config.php#L378-L397

And the second one is for system/core/Input.php. This lines:
https://github.com/bcit-ci/CodeIgniter/blob/0648ae2acb3fae84f2cc49e15edb345ccef6cad5/system/core/Input.php#L289-L352

Just replace those config and the set_cookie method

All 20 comments

That's cool, but there's some issues around it ... For one, right now we just proxy to setcookie() and SameSite isn't supported on it until PHP 7.3.0; it's even trickier with sessions.

Do you have ideas about how it should ideally work?

Hi @narfbg Andrey, If I understand your question, Samesite is a very cheap way of preventing CSRF attack. But it will break if a web site has multiple subdomains to communicate for its authenticated contents.

No ... I'm very well aware of what the SameSite attribute is and what it does.

What I'm asking is how would you implement it in CI?

It would be a very useful thing. I have applications that make CORS calls to each other.
Google in April 2020 will need SameSite cookies = none. https://www.chromestatus.com/feature/5633521622188032
image
A cookie associated with a cross-site resource at <URL> was set without theSameSiteattribute. A future release of Chrome will only deliver cookies with cross-site requests if they are set withSameSite=NoneandSecure. You can review cookies in developer tools under Application>Storage>Cookies and see more details at <URL> and <URL>

I see that session is already doing some php version checks like is_php(XXX), so why not using the same technique to identify the php version which supports this natively (e.g. 7.3) and implement it into the setcookie() call.

I guess that makes sense for sessions, though I'm not sure if I'd want that to be configurable at all there.

Tell you what - it's up for grabs and anyone who wants this should submit a pull request with an implementation.

Referencing it properly, so the threads get linked: #5874

if you have same issue like am having in shopify app written with CI
upgrade the php version 7.3.x and in your config file add these two lines will fix the issue in your config.php :

<?php
/*config.php*/
defined('BASEPATH') or exit('No direct script access allowed');

ini_set('session.cookie_samesite', 'None');
ini_set('session.cookie_secure', TRUE);

harmandeep-singh solution worked for me. (I am making CORS request) and Chrome Android samesite update broke my website in that browser, thanks Google!

I had this same CORS problem in my application. my PHP 7.2 and my CI 3.X.
I was able to resolve it by making only the following change to the applications / config / config.php file

$config['cookie_path'] = '/; SameSite=None';

I had this same CORS problem in my application. my PHP 7.2 and my CI 3.X.
I was able to resolve it by making only the following change to the applications / config / config.php file

$config['cookie_path'] = '/; SameSite=None';

That gives this Warning on PHP 7.3:
PHP Warning: Cookie paths cannot contain any of the following ',; \t\r\n\013\014' in ...system\core\Security.php:281

I had this same CORS problem in my application. my PHP 7.2 and my CI 3.X.
I was able to resolve it by making only the following change to the applications / config / config.php file
$config['cookie_path'] = '/; SameSite=None';

That gives this Warning on PHP 7.3:
PHP Warning: Cookie paths cannot contain any of the following ',; \t\r\n\013\014' in ...system\core\Security.php:281

@MichalSkoula if that is case try another solution :
ini_set('session.cookie_samesite', 'None');
ini_set('session.cookie_secure', TRUE);

@harmandeep-singh this solution works for non-ajax requests but when one ajax request occurs, then session library overwrites cookie and removes samesite attribute. (line 163)

The above works great for the session cookie but not csrf. the LAST 2 answers to this stackexchange are the current solution if you need both session and csrf: https://stackoverflow.com/questions/60634341/codeigniter-3-samesite-attribute-for-csrf-protection

unless there's another config setting that affects csrf samesite I'm not seeing mentioned anywhere.

Maybe like this?

/*
|--------------------------------------------------------------------------
| Cookie Related Variables
|--------------------------------------------------------------------------
|
| 'cookie_prefix'   = Set a cookie name prefix if you need to avoid collisions
| 'cookie_domain'   = Set to .your-domain.com for site-wide cookies
| 'cookie_path'     = Typically will be a forward slash
| 'cookie_secure'   = Cookie will only be set if a secure HTTPS connection exists.
| 'cookie_httponly' = Cookie will only be accessible via HTTP(S) (no javascript)
| 'cookie_samesite' = The SameSite cookie setting (Possible values: 'Lax', 'Strict', 'None', NULL, default: NULL)
|
| Note: These settings (with the exception of 'cookie_prefix' and
|       'cookie_httponly') will also affect sessions.
|
*/
$config['cookie_prefix']    = '';
$config['cookie_domain']    = '';
$config['cookie_path']      = '/';
$config['cookie_secure']    = FALSE;
$config['cookie_httponly']  = FALSE;
$config['cookie_samesite']  = NULL;
/**
     * Set cookie
     *
     * Accepts an arbitrary number of parameters (up to 7) or an associative
     * array in the first parameter containing all the values.
     *
     * @param   string|mixed[]  $name       Cookie name or an array containing parameters
     * @param   string      $value      Cookie value
     * @param   int     $expire     Cookie expiration time in seconds
     * @param   string      $domain     Cookie domain (e.g.: '.yourdomain.com')
     * @param   string      $path       Cookie path (default: '/')
     * @param   string      $prefix     Cookie name prefix
     * @param   bool        $secure     Whether to only transfer cookies via SSL
     * @param   bool        $httponly   Whether to only makes the cookie accessible via HTTP (no javascript)
     * @param   string|NULL $samesite   The SameSite cookie setting (Possible values: 'Lax', 'Strict', 'None', NULL, default: NULL)
     * @return  void
     */
    public function set_cookie($name, $value = '', $expire = 0, $domain = '', $path = '/', $prefix = '', $secure = NULL, $httponly = NULL, $samesite = NULL)
    {
        if (is_array($name))
        {
            // always leave 'name' in last place, as the loop will break otherwise, due to $$item
            foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'httponly', 'name') as $item)
            {
                if (isset($name[$item]))
                {
                    $$item = $name[$item];
                }
            }
        }

        if ($prefix === '' && config_item('cookie_prefix') !== '')
        {
            $prefix = config_item('cookie_prefix');
        }

        if ($domain == '' && config_item('cookie_domain') != '')
        {
            $domain = config_item('cookie_domain');
        }

        if ($path === '/' && config_item('cookie_path') !== '/')
        {
            $path = config_item('cookie_path');
        }

        $secure = ($secure === NULL && config_item('cookie_secure') !== NULL)
            ? (bool) config_item('cookie_secure')
            : (bool) $secure;

        $httponly = ($httponly === NULL && config_item('cookie_httponly') !== NULL)
            ? (bool) config_item('cookie_httponly')
            : (bool) $httponly;

        if ( ! is_numeric($expire) OR $expire < 0)
        {
            $expire = 1;
        }
        else
        {
            $expire = ($expire > 0) ? time() + $expire : 0;
        }

        if ($samesite === '' && config_item('cookie_samesite') !== '')
        {
            $samesite = config_item('cookie_samesite');
        }

        $samesite = is_string($samesite) ? ucfirst($samesite) : $samesite;

        if ( ! in_array($samesite, array('Lax', 'Strict', 'None', NULL), true))
        {
            show_error("The SameSite cookie setting should be one of: 'Lax', 'Strict', 'None' or NULL.");
        }

        // Issue: https://github.com/bcit-ci/CodeIgniter/issues/5791
        // Reference: https://stackoverflow.com/questions/39750906/php-setcookie-samesite-strict
        if (PHP_VERSION_ID < 70300) {
            setcookie(
                $prefix.$name,
                $value,
                $expire,
                $path.'; samesite='.$samesite,
                $domain,
                $secure,
                $httponly
            );
        } else {
            setcookie(
                $prefix.$name,
                $value,
                array(
                    'path' => $path,
                    'domain' => $domain,
                    'secure' => $secure,
                    'httponly' => $httponly,
                    'samesite' => $samesite
                )
            );
        }
    }

@esyede where do you put this solution please?

@otengkwame First code is for the application/config/config.php file. This lines:
https://github.com/bcit-ci/CodeIgniter/blob/0648ae2acb3fae84f2cc49e15edb345ccef6cad5/application/config/config.php#L378-L397

And the second one is for system/core/Input.php. This lines:
https://github.com/bcit-ci/CodeIgniter/blob/0648ae2acb3fae84f2cc49e15edb345ccef6cad5/system/core/Input.php#L289-L352

Just replace those config and the set_cookie method

Thanks so much @esyede

@otengkwame no problem

Was this page helpful?
0 / 5 - 0 ratings

Related issues

monski picture monski  路  4Comments

erikk1986 picture erikk1986  路  3Comments

Struki84 picture Struki84  路  7Comments

rjdjohnston picture rjdjohnston  路  4Comments

Tjoosten picture Tjoosten  路  6Comments