Cookies - an immortal piece of Web
Tags: webdev, programming, javascript
Published: 2021-01-21 22:04:49

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.

## What is a cookie?
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

```js
const request = {
  url: 'https://test.com',
  method: 'GET',
  headers: {
    Connection: 'keep-alive',
    Accept: 'application/json',
    Cookie: 'rodoStatus=accepted; uid=xxx;'
  }
}
```
### Response
```js
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](https://web.dev/samesite-cookies-explained/) which should explain everything.

## When you should use cookie?
### 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.
```js
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.
```js
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
```js
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](https://www.thinktecture.com/en/identity/samesite/samesite-in-a-nutshell/#the-chrome-vs-safari-issue) 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](https://www.convert.com/blog/privacy/itp-2-2-impact-abtesting-users/).
- Firefox with ETP ([Enhanced Tracking Protection](https://blog.mozilla.org/blog/2019/06/04/firefox-now-available-with-enhanced-tracking-protection-by-default/)) might also block various cookies, but mostly third-party tracking ones as default.

### Max cookie size
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](https://datatracker.ietf.org/doc/rfc6265/?include_text=1).

### 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](https://www.npmjs.com/package/js-cookie) or write their own helper for that in **every** project.

But there is a light in a tunnel - [Cookie Store API](https://wicg.github.io/cookie-store/) created by Google and implemented since Chrome 87. Sadly other browsers vendors aren't open for implementation yet.

### References
- [Cookies basics on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
- [Cookie header on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie)
- [Set-Cookie header on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)

PS This is my first published article, so I'm waiting for constructive criticism 🙄😬