Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel shared cookie detection issue in domain and subdomain

I am working on Laravel 5.4.30.

Imagine that we have a domain example.com and a subdomain of dev.example.com. The main domain is for master branch and the dev subdomain is for develop branch. We have cookie notice system that will be hidden after clicking on Hide Cookie Notice button. This works by setting a cookie forever. We have set the SESSION_DOMAIN configs to each domain for each environment.

For main domain:

SESSION_DOMAIN=example.com

For dev subdomain:

SESSION_DOMAIN=dev.example.com

Now the issue comes from here. If we go to the example.com and click on hiding the cookie notice, a cookie will be set forever for main domain. After that we go to the dev.example.com and do the same. So a cookie will be set for subdomain as well. But this cookie has been set after previous one. (The order is important) Now if we refresh the subdomain, we will see that notice again! (not hidden) The browser has read the main cookie because of .example.com set in domain parameter of cookie in the browser, so every subdomain will be affected. But the view still shows the notice because it cannot read any cookie for hiding.

Anyway I don't want to share that cookie across all subdomains. How can I achieve that? I think I should add a prefix for cookie name. But I don't know how to do it, that laravel automatically adds prefix to cookie name.

Any solutions?

like image 723
Misagh Laghaei Avatar asked Aug 18 '17 15:08

Misagh Laghaei


3 Answers

You need to implement your own "retrieving" and "setting" a cookie.

Retrieving (has, get) cookies

Create yourself new class (anywhere you like, but I would do app/Foundation/Facades/) with name Cookie.

use \Illuminate\Support\Facades\Cookie as CookieStock;

class Cookie extends CookieStock { 
    //implement your own has(...);
    public static function has($key)
    {
        return ! is_null(static::$app['request']->cookie(PREFIX . $key, null)); //get the prefix from .env file for your case APP_ENV
    }

    //implement your own get(...);
    public static function get($key = null, $default = null) {...}
}

Now open up config/app.php and change corresponding alias (cookie).

Setting (make) cookies

Create yourself new provider (use artisan), and copy-paste code from Illuminate\Cookie\CookieServiceProvider.php and change namespaces. Again open up config/app.php and change corresponding service provider with the new one.

Create yourself new class (anywhere you like, but I would do app/Foundation/Cookie/) with name CookieJar.

use \Illuminate\Cookie\CookieJar as CookieJarStock;

class CookieJar extends CookieJarStock {

//Override any method you think is relevant (my guess is make(), I am not sure at the moment about queue related methods)

    public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true)
    {
        // check before applying the PREFIX
        if (!empty($name)) {
            $name = PREFIX . $name; // get the PREFIX same way as before
        }

        return parent::make($name, $value, $minutes, $path, $domain, $secure, $httpOnly);
    }
}

Update the code in your own cookie service provider to use your implementation of CookieJar (line 19).

Run $ composer dump-autoload, and you should be done.

Update

Since BorisD.Teoharov brought up, that if framework changes signature of CookieJarStocks make() (or any other cookie related function) in between the major versions, I made a example repository, that includes a test that can be used as is and it will fail if signature change happens.

It is as simple as this:

public function test_custom_cookie_jar_can_be_resolved()
{
    resolve(\App\Foundation\Cookie\CookieJar::class);
    $this->assertTrue(true);
}

Detailed how to can be inspected in the corresponding commit diff.

like image 151
Kyslik Avatar answered Nov 01 '22 22:11

Kyslik


I've setup test environments to make sure, I'm not missing any details.

As in my former answer, I thought invalidating cookies will be sufficient for that case, but as @BorisD suggested it is not, and I've confirmed that on my tests.

So there are a few important notes, coming from my experiences...

  1. Don't mix Laravel versions in subdomains - If using SESSION_DOMAIN you need to make sure your Laravel version matches (between root and subdomains), cause I've experimented with 5.4 under example.com domain and 5.6 under dev.example.com. This showed me some inconsistency in dealing with Cookies, so some important changes have been done, between these versions, and you can be sure it will not work correctly if you mix versions. I finally ended up with Laravel 5.6 on both domains, so I'm not 100% sure if that works on Laravel 5.4, but I think it should.

  2. Make sure all your subdomains use the same APP_KEY - otherwise, Laravel will be unable to decrypt the Cookie, returning null value, cause all encryption/decryption in Laravel uses this app key...

  3. SESSION_DOMAIN. In SESSION_DOMAIN I've pointed the same root domain like example.com for both domains. With this setting, I can create a cookie on root domain, and retrieve it correctly on both domains. After that setting, creating a cookie on subdomain forces root domain to receive new value from subdomains cookie also, and they are overridden. So I guess everything works here as requested in the original question.

  4. Cookie make parameters - In case you want to use a subdomain in SESSION_DOMAIN, you can safely do that also. However, you need to make sure, important let's call them global cookies are defined in a bit different way. Cookie make syntax:

    Cookie make(string $name, string $value, int $minutes, string $path = null, string $domain = null, bool $secure = false, bool $httpOnly = true)

    So what's important here, you need to put your root domain for this particular cookie on creation like this for example: return response($content)->cookie('name','value',10,null,'example.com')

Conclusions:

  1. With this config, you should be able to access your Cookies properly under subdomains and your root domain.
  2. You may probably need to update your Laravel installations to 5.6, which will force you to upgrade to PHP 7.1 at least (there were some changes to cookies in php also)
  3. And finally, in your code, don't rely on Cookie existence, but on its values only (I don't know if that's in your case).
like image 2
Bart Avatar answered Nov 02 '22 00:11

Bart


You could set a prefix for the cookie name depending on the environment.

First, add COOKIE_PREFIX to your env file.

COOKIE_PREFIX=dev

Then, use it when setting your cookie

$cookie = cookie(env('COOKIE_PREFIX', 'prod') . '_name', 'value', $minutes);

Then, retrieve it like so

$value = $request->cookie(env('COOKIE_PREFIX', 'prod') . '_name');
like image 1
Daniel Avatar answered Nov 01 '22 22:11

Daniel