Even though I’ve been doing web things for a while now, I confess I had never dealt with browser cookies other than clicking those cookie notifications on every other website you visit these days.

I mean, I knew that it was a form of storage on the browser, but I’d always used localStorage for that. Recently I was working on something that used browser cookies and I figured it was a good time to figure them out.

I love the name cookie, but I can’t help but wonder if there was a reason for it. Turns out I’m not the only person who had that question. And the inventor of browser cookies, Lou Montulli explained that he had heard the term ‘magic cookie‘ from an operating systems course in college that had a similar meaning to the way his proposed solution for a session identifier worked.

The original problem he was trying to solve was the implementation of an online shopping cart, which eventually led to the original specification for persistent client state, and has since evolved into the current RFC 6265. The first cookies were used to verify repeat visitors to the Netscape website.

A cookie is a small plain text file stored in the browser. There isn’t anything executable in there. It simply contains a small amount of data. Every browser stores them in a slightly different location (e.g. Where cookies are located in Windows 10, for all web browsers).

The data in the cookie is sent over by the server, stored on the user’s browsers, then used in subsequent requests as an identifier of sorts. Cookies are mainly used to remember state (if you are logged in, shopping cart items, user preferences etc.) as well as tracking.

Cookies are created when the server sends over one or more Set-Cookie headers with its response, something along these lines:

Set-Cookie: NAME=VALUE

It could be any name-value pair, but each cookie can contain only 1 name-value pair. If you need more than 1 cookie, then multiple Set-Cookie headers are needed. An example of a server sending over cookie headers to the browser looks something like this:

HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: viola=red_panda
Set-Cookie: mathia=polar_bear

As a frontend developer, I must admit I don’t debug server-sent headers very often so this is not something I see on a regular basis. Once the cookie is set, all subsequent requests to the server from the browser will also have the cookies in its request header.

GET /demos/cookie/ HTTP/2
Host: huijing.github.io
Cookie: viola=red_panda; mathia=polar_bear

Even though cookies are usually created on the server, you can also create them on the client-side with Javascript, using document.cookie. Browser cookies also have a number of attributes in addition to the name-value pair mentioned earlier.

The cookie name can be any US-ASCII characters except control characters, spaces, tabs or separator characters. The cookie value can be optionally wrapped in double quotes and be any US-ASCII characters except control characters, double quotes, commas, semicolons, backslash and whitespace.

Adding special prefixes to the cookie name also forces certain requirements. If your cookie name starts with __Secure-, it must be set with the secure flag from a page served with HTTPS. If your cookie name starts with __Host-, it must be set with the secure flag from a page served with HTTPS, it must not have a domain specified and its path must be /.

The rest of the attributes are optional but can impact cookie behaviour significantly depending on what values are set.

When a cookie passes its expiry date, it will no longer be sent with browser requests, and instead will be deleted. The date value is a HTTP timestamp.
Also related to a cookie’s expiry, but in seconds. After the specified amount of time, the cookie will expire, so setting it to 0 or a negative number means instant expiry. Max-Age takes precedence over Expires if both are set.
Specifies the host where the browser cookie gets sent to. Only a single domain is allowed. If not present, this defaults to the current document URL’s host. When specified, all sub-domains are included as well.
Cookie will only be sent if the path exists in the current URL
Cookie will only be sent when the request is made with HTTPS
Javascript cannot access the cookie through document.cookie (to mitigate XSS attacks)
Specifies if a cookie is sent with cross-origin requests.
  • Strict means the cookie is only sent for requests originating from the same URL as the current one.
  • Lax means the cookie is not sent on cross-site requests, but will be sent if the user navigates to the origin site from an external site.
  • None means the cookie will be sent on both same-site and cross-site requests, but can only be used if the Secure attribute is also set.

If you use Firefox, you may notice a console log message like this on some websites.

Console message in Firefox stating Some cookies are misusing the SameSite attribute, so it won't work as expected

Back in August 2020, Mozilla made the decision to treat cookies as SameSite=Lax by default, and require cookies with SameSite=None to also set the Secure attribute. The original behaviour for cookies was SameSite=None but this leaves users susceptible to Cross-Site Request Forgery attacks.

Both Chrome and Firefox has rolled this out, but it seems like only Firefox displays the console log warning? If you can verify the console logging situation, please let me know.

Using cookies to do stuff

Cookies without an Expires or Max-Age attribute are treated as session cookies, which means they are removed once the browser is closed. Setting a value on either Expires or Max-Age makes them permanent cookies, since they will exist until they hit their expiry date.

Again, I usually don’t do server-side stuff so I’ll only talk about messing around with cookies on the client-side. The Document has a cookie property that lets us read and write browser cookies via Javascript.

To see all cookies associated with the document, use document.cookie. You can type this in the browser’s console and see something like this:

Running document.cookie in the browser console

To create a new cookie, you can do something like this:

document.cookie = "xiaohua=tortoise"

If you need more than one cookie, you’ll have to do this for every cookie you want to create.

Create a new cookie in the browser console

Even if you refresh the page, the cookie should still be there. To get rid of the cookie, or reset it, you can set the Expires value to the beginning of time itself, Thu, 01 Jan 1970 00:00:00 GMT. I’m semi-kidding. Just in case you never heard of this interesting (and fairly important) piece of trivia, I shall quote MDN:

A JavaScript date is fundamentally specified as the number of milliseconds that have elapsed since midnight on January 1, 1970, UTC. This date and time are not the same as the UNIX epoch (the number of seconds that have elapsed since midnight on January 1, 1970, UTC), which is the predominant base value for computer-recorded date and time values.

For example, if I wanted to get rid of the taria cookie, I would do this:

document.cookie = "taria= ;expires=Thu, 01 Jan 1970 00:00:00 GMT"
Reset a cookie in the browser console

Because cookies are strings, doing things based on cookie data involves mostly string manipulation. So I won’t go into that in detail, but here’s a ridiculous demo you can play around with, ideally with DevTools open. It just randomly assigns a group cookie, then shows you something different based on that.

Screenshot of cookie demo site

Update: Thomas Steiner shared about the Cookie Store API and its polyfill which lets us avoid having to the do not fun string manipulations mentioned above.

Wrapping up

It’s been a while since I last published anything. I suppose this is the longest hiatus I’ve had since I started this blog, but somehow being stuck in the same place doesn’t seem to motivate me to write words. But we’ll see.

Meanwhile, go eat some of your favourite cookies.

Credits: OG:image from Red Panda Loves Cookies video on Oregon Zoo Youtube channel