Understanding Magento 2 cookies
Over the last few days, I’ve explored how Magento 2 uses cookies. From updating cart to managing cached pages to switching store views, cookies play an important role. This exploration is a result of weekend log debugging session. Our online store wasn’t working correctly when switching between different store views and sometimes it would go in redirection loop. Sometimes admin login wouldn’t work. These problems would temporarily go away once cookies were cleared from browser so it was obviously cookie-related problem.
The Setup
First thing I needed to do was setup same environment on local and replicate the bug on local. I installed new clean instance of Magento 2.1.6 . Then I created 2 store views under Main website. Result was I was able to run three different subdomains www.mage.dev , view1.mage.dev view2.mage.dev. This was all done on nginx-fpm stack. I set cookie domain to .mage.dev and left cookie path to default.

The issue was replicated. When I switched between store views using store switcher more than three four times, site would go in redirection loop. More over, if I logged in view1 and switched to default store view, welcome message would disappear. The number three-four might seem ridiculous now but read on it will make sense. Carefully examine the screenshots below:


As you start switching more and more, number of 302 (temporary redirect) responses increase in network tab.


Finally when I click on customer login, it goes in a redirection loop.
Why does this happen?
To understand this, you have to understand cookie domain. I set it as .mage.dev, that means all cookies with this domain will be shared between my subdomains ie. www.mage.dev, view1.mage.dev view2.mage.dev. This is very useful when you want to share customer session between sub-domains.

First two columns are name of the cookie and url-encoded value. Domain column is what interest us now. There are cookies with domain name .mage.dev and these will be shared between sub-domains. Ergo, PHPSESSID will be available to all sub-domains. That means customer sessions and cart will be shared. Except for form_key and PHPSESSID, all other cookies are sub-domain specific. These will be sent only to the respective sub-domain. These cookies are called host-only cookies. When you don’t specify the domain while sending cookies, they are set to host only. All cookies with with www.mage.dev will only be sent when this particular sub-domain is accessed. This is good because there might be some data that is sub-domain specific.
Now that we understand a little bit about how cookies work, we can fully understand how store switcher works. Every link in in store switcher is a form action which calls Magento\Store\Controller\Store\SwitchAction controller. Observe the code in execute method
$currentActiveStore = $this->storeManager->getStore();
$storeCode = $this->_request->getParam(
StoreResolver::PARAM_NAME,
$this->storeCookieManager->getStoreCodeFromCookie()
);
try {
$store = $this->storeRepository->getActiveStoreByCode($storeCode);
} catch (StoreIsInactiveException $e) {
$error = __('Requested store is inactive');
} catch (NoSuchEntityException $e) {
$error = __('Requested store is not found');
}
if (isset($error)) {
$this->messageManager->addError($error);
$this->getResponse()->setRedirect($this->_redirect->getRedirectUrl());
return;
}
$defaultStoreView = $this->storeManager->getDefaultStoreView();
if ($defaultStoreView->getId() == $store->getId()) {
$this->storeCookieManager->deleteStoreCookie($store);
} else {
$this->httpContext->setValue(Store::ENTITY, $store->getCode(), $defaultStoreView->getCode());
$this->storeCookieManager->setStoreCookie($store);
}
Code from line 22 to 28 says that, if we are switching to default store view then delete the cookie names “store” else set a cookie named store with the value of current store code. This is all good BUT cookie domain is not specified while sending this store cookie. This is precisely where the mess begins. www.mage.dev can not delete store cookie set by view1.mage.dev. Hence when you go to chrome://settings/cookies you see this

For www.mage.dev the store cookie should have been deleted. Instead it’s set to store code ‘view1’. Same case again for view1.mage.dev

How do I fix this??
Default M2 code will work just fine when “use store code in url” option is set to yes. But that way you can not use subdomains. You will be accessing different stores like mage.dev/view1, mage.dev/view2. This way, store cookie is shared.
To solve this we have to set cookie domain while sending store cookie. Store Cookie is set in Magento\Store\Model\StoreCookieManager. It implements Magento\Store\Api\StoreCookieManagerInterface . We have to change how setStoreCookie is implemented. You can write you own class and replace the default one using di.xml. Your setStoreCookie and deleteStoreCookie functions should look something like
/**
* {@inheritdoc}
*/
public function setStoreCookie(StoreInterface $store)
{
$cookieMetadata = $this->cookieMetadataFactory->createPublicCookieMetadata()
->setHttpOnly(true)
->setDurationOneYear()
->setDomain($store->getConfig('web/cookie/cookie_domain'))
->setPath($store->getStorePath());
$this->cookieManager->setPublicCookie(self::COOKIE_NAME, $store->getCode(), $cookieMetadata);
}
/**
* {@inheritdoc}
*/
public function deleteStoreCookie(StoreInterface $store)
{
$cookieMetadata = $this->cookieMetadataFactory->createPublicCookieMetadata()
->setDomain($store->getConfig('web/cookie/cookie_domain'))
->setPath($store->getStorePath());
$this->cookieManager->deleteCookie(self::COOKIE_NAME, $cookieMetadata);
}
Now when you switch stores you will see only one 302 redirect. So that’s all about cookie domain.
BUT
Remember I said PHPSESSID is shared between all sub-domains. So does that mean this same PHPSESSID will also be sent when you access www.mage.dev/admin ?? Yes. But admin uses “admin” cookie for validating admins. Interesting thing about this cookie is, it’s path is set to ‘/admin’ or whatever your admin URI is. This way, this “admin” cookie is sent to server only when path begins with www.mage.dev/admin. That is the importance of cookie path. If the cookie path is set to ‘foo/’ then your request path must start with foo ie. domain.com/foo/bar/something.
Now that we understand cookie domain, cookie path and host-only cookies we can start understanding how Magento uses cookies for customer data, cart data, FPC. In next post we shall cover other cookies that interest me like private_content_version, x-Magento-Vary. Adios!
Comments