Cookies - an immortal piece of Web

Cookies are still a great idea for many use cases, so let me reintroduce them.

After localStorage was implemented in major browsers, I often saw that it was used as a replacement of plain old cookies. Even though developers had to handle e.g. expiration implementation on they own.

Don't get me wrong, I'm very fond of localStorage and I'm using it on daily basis, but cookies are also very useful and even more suited to some use cases.

But let's start from the beginning and explain what cookie is. Cookie is stored information. It consists of Name, Value and additional attributes like Expires, Domain, Path and more.

It is attached to the http request (client -> server) and response (server -> client) in form of header(s). Requests use Cookie header where all cookies are transferred. Responses on the other hand use multiple Set-cookie headers (one per cookie). Let me visualise it to you in a form of plain objects:

Request

const request = {
  url: 'https://test.com',
  method: 'GET',
  headers: {
    Connection: 'keep-alive',
    Accept: 'application/json',
    Cookie: 'rodoStatus=accepted; uid=xxx;',
  },
};

Response

const response = {
  status: 200,
  data: {
    email: 'test@test.com',
  },
  headers: {
    Connection: 'keep-alive',
    'Content-length': 20,
    'Set-cookie': 'rodoStatus=accepted; Max-Age=31536000; Domain=test.com; Path=/;',
    'Set-cookie': 'uid=xxx; HttpOnly;',
  },
};

As you can see, request cookies don't have any attributes declared, because they are only important for a sender (client), not for a receiver (server).

Cookies might be "customized"

Before I describe use cases I have mentioned at the beginning, I want to do quick introduction to cookies attributes, because without knowledge about them it might be hard to understand cookies potential.

Each and every cookie might have additional attributes:

  • Domain - it tells browser in which domain cookie has to be visible. Cookie with domain set to test.com, will be accessible only in pages under test.com domain. Sometimes is handy to make cookie available in subdomains. To do that, domain attribute has to be prefixed with a dot like .test.com.
  • Path - it points browser under what path cookie will be available. Cookie with path set to /page, will be accessible under /page and /page subpaths like /page/2 etc.
  • Expires - it declares date and time until when cookie will exist. After that, it will be automatically deleted.
  • Max-Age - it behaves similar to Expires attribute, but instead of date and time it uses number of seconds for how long cookie will exist.
  • Secure - it prevents cookie from being send to the server on other than https protocol when declared.
  • HttpOnly - it limits visibility of cookie. If declared, it's not possible to access cookie value through document.cookie, but it's still send with requests made with e.g. fetch().
  • SameSite - it accepts three values - Strict, Lax (default in most browsers) and None. It's very complex topic which won't be covered in this article. If you want to know more here is a great article which should explain everything.

Automatic expiration

When you want to store some information for "forever", localStorage is a match. But if you want to store data only for, let's say - 7 days, it might be problematic, because you will have to add some kind of expires property and check it with setInterval and after each page load.

Cookies for a rescue! Just set Expires attribute and you don't need to worry about it any more.

document.cookie = 'uid=xxx; Max-Age=604800'; // 60 * 60 * 24 * 7 - 7 days

Share data implicitly

If you know, that specific piece of data should be transferred between a client and a server, it might be good idea to use a cookie, because every cookie will be automatically attached to a request and all cookies returned in a response will be saved without any manual work.

Hide data from a client

Sometimes there is a need to store some information, but it shouldn't be available via JavaScript e.g. access token. Cookies are great for that. Just use HttpOnly attribute and that's it.

document.cookie = 'at=xxx; HttpOnly;';
console.log(document.cookie); // returns ""

Access data from a server

Let me show you real world example. When you want to implement dark theme on your website, you have to store chose theme somehow. And based on this stored value you will add theme-dark or theme-light class to <body>.

You might store value with localStorage.setItem("theme", "dark") after user toggle theme switcher. And after page load get value to <body> with

document.body.classList.add(`theme-${localStorage.getItem('theme')}`);

But one problem could come up, especially for users with slower devices. For a moment, between DOM render and script execution there will be small time window, where <body> won't have any class (or it will have default one). And because of that, user might see flickering effect.

Fortunately, cookies are also suited for this job, because you can access them on a server and set the right theme before page will be rendered by the browser.

Potential problems with cookies

Browsers security systems

Currently, all browsers are making cookies implementations more strict. Some examples below:

  • In 2020 default value of SameSite attribute was set to Lax. Previously it was set to None. It will be perfectly fine if browsers unified implementations, which they didn't and in each browser SameSite attribute behaves differently. If you are interested in this topic, this article is very thorough.
  • In private windows, browsers usually block third-party cookies, so your web app might behave differently there.
  • Safari and their ITP (Intelligent Tracking Prevention) might remove cookie after just 1 day, even when their Expires/Max-Age attribute points to further date. More about that you can read in this article.
  • Firefox with Enhanced Tracking Protection might also block various cookies, but mostly third-party tracking ones as default.

Cookies are restricted in their size and quantity. Most browsers allow for 50 cookies per domain, 4096 bytes per cookie, 3000 cookies in total based on RFC 6265.

Outdated JavaScript API

As you might see API for cookie manipulation in the browser is a bit... inaccessible.

Setting a new cookie is fairly easy - document.cookie = "uuid=xxx; SameSite=Strict; Secure;", but nightmare comes when we need to get cookie value by name. It is not possible at the moment out of the box, because document.cookie will return all available cookies in one string. So, developers have to include some libraries like js-cookie or write their own helper for that in every project.

But there is a light in a tunnel - Cookie Store API created by Google and implemented since Chrome 87. Sadly other browsers vendors aren't open for implementation yet.

References


Created:
Updated: