Removing cookies

Cookies are hard to manage. As you'll know, the cookie API is ... infelicitous. You can set a cookie like so:

> document.cookie='name=value; Path=/; Domain=kenneth.kufluk.com; Max-Age=1';

When a cookie is set by the server, it uses a similar format, in the "set-cookie" header of the response.

Reading the cookie back just gives you the serialized name/value pairs:

> document.cookie;
< "name=value;another=value"

When the cookies are sent to the server in the request they're also just the name/value pairs.

What this means is that the full metadata of a cookie is never available, to the client or the server, except when it is set.

If you want to delete all the cookies for your website, this is tricky. You can delete a cookie by providing a new cookie if the same name with an expiry date in the past. However, cookies are partitioned by domain and path. If those aren't set appropriately on the deletion, you won't clear the right cookie.

(There is a new header "Clear-Site-Data" if you want a nuclear option, which will instruct supporting browsers to remove all cookies.)

Given that we need to set a cookie to delete a cookie, we will need to know the name, domain, path and other meta data of the cookies to be able to delete them. We only know the names of cookies on the current page (ie, based on the current path and domain), and the path/domain/meta info for those cookies is not available.

In other words, it's important to issue your cookies appropriately in the first place.

Always set the Path to root (/)

Always set the Domain

If our cookies can expire within a reasonable timeframe then there's little need for deletion.

A common solution is to use session cookies. If you don't specify an expiry or a max-age when setting a cookie, it's set to the "session" and is deleted when the browser window is closed. In an old browser context where we had one site per window, this made sense. However, modern browsers are able to preserve and restore pages, windows and tabs even after a reboot. The session cookies are no longer short-length, they're indefinite length. During a recent survey of cookies at Twitter, we observed session cookies in requests that we hadn't issued for three years.

There are two different kinds of cookies often called "session cookies", which can be confusing. I'm using it here to mean a non-persistent cookie that should be removed at the end of a "session". It is often also used to describe a cookie that contains a serialized set of values.

Always set the Expires or Max-Age header (max-age is not supported by older browsers, so expires is still prefered)

Given that we now have cookies with are properly issued with expiry dates, we should question what a reasonable expiry time would be.

Let's consider a couple of examples.

We show a tooltip pointing to a new feature of the site. When a user has dismissed that tooltip, we issue a cookie so that the user doesn't have to see it again. We don't ever want the user to lose that cookie, so we set the expiry time to "infinite", which our cookie library helpfully sets to the year 9999. In other words, eight thousand years from now.

Every cookie you set with be attached to every request. While small, these bytes can add up over time, and can cause issues. We call this request bloat. An issue we see at Twitter is when the cookie size, combined with other headers, exceeds the limit of our http framework. These requests are immediately rejected with a status 431, which is bad for users, because they won't know why the request failed and won't be able to submit a similar request until they clear out some cookies.

Another example is your login. When you log in, we issue a cookie representing your credentials. The cookie allows you to make subsequent requests as that user. We set that cookie for a month.

Let's consider those expiries against expectations of a user. If you take a vacation for a month, then reopen your laptop, would you expect either of those cookies to disappear? Probably not. Are you likely to be on vacation for 8000 years? Probably not.

I think we could set realistic grounded values here, based on common sense. If you leave your computer for more than three months, it wouldn't be too much of a hassle to log in again. If you saw an educational tooltip 18 months after you first saw it, that mightn't be too annoying (assuming the code is still in the site).

However, cookie expiries don't work that way for the login cookie. It's not measuring the time since the cookie was last used, it's measuring the time since the cookie was issued. The best solution here is to keep a rotating value managed by the server. Store the login cookie value in a table on the server. If it's seen and it's more than 30 days old, issue a new one. If it's seen and it's more than 90 days old, consider it expired. By checking the expiry on the server not the client, we can set the cookie expiry to anything reasonable over 90 days.

Consider 18 months a maximum lifetime for your cookie

Manage login expiries on the server side

Refresh/reset cookies that you want to keep longer

If your site has lost its login cookie, it might find itself in a bad state. Maybe you have cached content in the serviceworker, in other cookies, in localStorage, in indexedDB.

In this case, we have historically cleaned up the user storage as if they had been logged out, but we found this caused problems for users, where they were unexpectedly cleaned up. It turns out that some privacy-protection browser extensions can strip cookies from the first request. A common example is Privacy Badger, which strips the cookies from the page requests if the page is served by the serviceworker. As these extensions are common, the developer should guard against them by checking login via XHR and either refreshing the page, or popping a dialog asking for advice.

Since cookies are a distributed store of data that is hard to read from and manage, it's important to be careful about which cookies you issue and when, and try to limit those cookies as much as possible. Storage such as localStorage should be used in preference, where possible.

Cookie spec: https://tools.ietf.org/search/rfc6265