Alexander Nnakwue Software engineer. React, Node.js, Python, and other developer tools and libraries.

The async Cookie Store API: A new treat for web developers

6 min read 1911

Async Cookie Store API

Background

Cookies are one of the oldest ways to store information in the browser. They were designed to be a reliable mechanism of storing stateful or session information for the stateless HTTP protocol. This kind of information generally comprises an authenticated user’s browsing activity or behavior, such as the pages they have visited or the links they have clicked.

This is how cookies work in a nutshell: after receiving an HTTP request, a server can send one or more Set-Cookie headers with the response back to the client. The cookie is usually stored by this client, and it can then be sent with requests made to the same server inside a Cookie HTTP header. Therefore, cookies are used to tell whether requests came from the same browser client session.

The problem with cookies

While cookies solved an important use case, they posed lots of problems. Their interface was quite complicated, as saving all of the cookies in document.cookie — which is part of the DOM — was problematic. There was no way of telling the result of performing a write. The document script needs to issue a read or get request to verify the result of a previous write or create request.

Another problem is that when the attributes of a cookie are read, they still need to be parsed/serialized. This is because they are all returned as a single string, with each cookie’s name-value pair concatenated into a list of pairs, and each list item separated by a semicolon.

This posed another challenge when generally managing cookies or performing actions like getting a cookie, checking for the existence of a cookie, checking for the existence of a particular value in the cookie data, and so on.

These days, developers are strongly encouraged to use modern storage APIs like IndexedDB or localStorage for storing session data in the browser. This is because they have larger storage limits than cookies, and cookie information from the client is never sent to the server.

If you still have a strong case for using cookies in your projects, however, you’re in luck: the async Cookie Store API presents a new and improved way of doing things.

How cookies work: A primer on web cookie behavior

Here, we will explore the behavior and complexities associated with cookies so that we can begin to appreciate the usefulness of the new async API.

While cookies are widely used today, their interface has been a source of complexity and performance issues. The document.cookie property lets us read and write cookies associated with the document. This document serves as a getter and setter for the actual values of the cookies.

However, whenever we use the document.cookie getter, the browser has to stop executing JavaScript until it has the cookie information we requested. Of course, this can lead to issues with the UI/UX. Let’s explore the read and write cookie operations below, so we can get a better feel.

We made a custom demo for .
No really. Click here to check it out.

Get cookies

Getting a specific cookie value always seemed like a very difficult thing to do. We had an option of iterating over the document.cookie value, which is a full string containing all of the cookies. Let’s see an example of accessing a cookie value with a particular name:

document.cookie = "name1=value1";
document.cookie = "name2=value2";
document.cookie = "name3=value3";

// to get a cookie value with name value2
const cookieValue = document.cookie
  .split('; ')
  .find(row => row.startsWith('name2'))
  .split('=')[1];

console.log(cookieValue) // value2

A more generic way of doing this, via this enduring Stack Overflow thread, would be:

const getCookie = (name) => {
  return document.cookie.split('; ').reduce((r, v) => {
    const parts = v.split('=')
    return parts[0] === name ? decodeURIComponent(parts[1]) : r
  }, '')
}

Set cookies

The API for setting a cookie value seems so old-fashioned — why? Well, after setting cookie data, there is no way for us to know that the cookie was successfully created.

A workaround is to use a getCookie function, like the one above, to loop over the cookie string to find the values we set for the cookie data.

document.cookie = "name=value";

Another issue when setting cookies is that there are no well-defined mechanisms for reporting cookie storage errors. This, of course, is due to document.cookie being synchronous.

Introducing the async Cookie Store API

Tired of the weird ways of getting cookies from document.cookie? Unsure whether the cookie you set was actually created? This section addresses those concerns.

The new Cookie Store API aims to improve all the shortcomings of working with cookies by providing an asynchronous alternative to document.cookie and exposing these cookies to service workers. The API provides a robust and logical method for cookie management. In summary, the API makes it easier to:

  • Avoid too much activity on the main thread by accessing cookies asynchronously
  • Avoid polling for cookies because changes to cookies can now be observed or monitored
  • Access cookies from service workers

Note: Due to the synchronous design of the document.cookie API, cookies were formerly inaccessible to the scope of service workers.

Service workers need to be able to read and modify the cookies accessible to pages under their scope since they are intended to act as HTTP proxies of some sort. Also, they need to quickly react to changes in session state, which will help in the clean up of old or stale cached data.

The new Cookie Store API contains methods for observing cookie changes (instead of polling) in documents and service workers. The methods for observing cookie changes include the ability to have a service worker activated when a cookie changes its value.

Using the async Cookie Store today

To make use of the Cookie Store API today, we can enable the origin trial flag. At the time of writing, however, the origin trial flag is closed as the API is currently undergoing major improvements based on community feedback. The origin trial is expected to be reopened after the maintenance and improvements are complete. You can find more details here.

With that said, you can still try it out locally — the API can be enabled on the CLI, which enables the API globally in Chrome for the current session. See the command below:

chrome --enable-blink-features=CookieStore

Alternatively, we can enable the #enable-experimental-web-platform-features flag in chrome://flags.

Introduction to querying, modifying, and monitoring cookies with the Cookie Store API

Querying/reading a cookie

Both document window and service workers access the same query API via the cookieStore property on the global object. The get() and getAll() methods on CookieStore are used to query cookies. Don’t forget that this returns a promise, which allows us to check for errors easily.

They take the same arguments, which can be:

  • A name
  • A list of options (this is optional for getAll())

The get() method is essentially a form of getAll() that only returns the first result. Here’s an example:

try {
    const cookie = await cookieStore.get('session_id');
    if (cookie) {
    console.log(`Found ${cookie.name} cookie: ${cookie.value}`);
} else {
    console.log('Cookie not found');
}
} catch (error) {
  console.error(`Cookie store error: ${error}`);
}

The objects returned by get() and getAll() contain all the relevant information in the cookie store, not just the name and the value, as in the older document.cookie API.

Modifying/writing a cookie

Also, both documents and service workers access the same modification API via the cookieStore property on the global object. Cookies are created or modified (written) using the set() method.

try {
    await cookieStore.set('opted_out', '1');
} catch (error) {
    console.error(`Failed to set cookie: ${error}`);
}

Keep in mind that the change is only guaranteed to be applied after the promise returned by cookieStore.set resolves.

Deleting a cookie

Cookies are deleted (expired) using the delete() method.

try {
  await cookieStore.delete('session_id');
} catch (error) {
  console.error(`Failed to delete cookie: ${error}`);
}

Under the hood, deleting a cookie is done by changing the cookie’s expiration date to the past, which still works.

Monitoring cookies

The Cookie Store API brings an alternative method for observing cookie changes, which does not require polling. A popular application for accessing cookies from JavaScript is detecting when the user logs out, and updating the UI accordingly.

Change events are fired for all relevant cookie changes. A simple example to register for change events is shown below –

cookieStore.addEventListener('change', event => {
    console.log(`${event.changed.length} changed cookies`);
 for (const cookie in event.changed)
    console.log(`Cookie ${cookie.name} changed to ${cookie.value}`);
  for (const cookie in event.deleted)
    console.log(`Cookie ${cookie.name} deleted`);
});

This API is also designed to allow browsers to batch change events for performance reasons. More details can be found here.

Extension to service workers

Service workers needing access to cookies cannot rely on the synchronous, blocking the document.cookie interface. This is because service workers cannot block the event loop, as that would interfere with the handling of other events.

However, the Cookie Store API is asynchronous, and therefore is allowed in service workers. Note that service workers might sometimes need cookie access, for instance:

  • To ensure private data is only displayed or sent while cookies that represent user authentication in an unexpired session are still present
  • To ensure data for one user account is not inadvertently displayed after a session cookie changes

Interacting with the cookies works the same way in both document contexts and in service workers. However, observing cookie changes is a bit different in service workers. This is because waking up a service worker can be pretty expensive; thus, there needs to be an explicit description of the cookie changes that the worker is interested in.

More details can be found in the draft document here. Also, we can refer to the section on change events in service workers in the explainer doc.

Conclusion

This exciting proposal provides an asynchronous cookie API for the following document events: set, delete, and read operations. Perhaps most importantly, it will allow service workers to read cookies, which isn’t possible today; reading and writing cookies from the document is currently a synchronous process, causing slow and annoying page load times. Asynchronous access to cookies addresses these needs.

The API includes a cookiechange event that will wake up service workers. Cookie change events in service workers are fired against the global scope, but an explicit subscription is required, associated with the service worker’s registration. More details here.

The API will also have a well-defined mechanism for reporting cookie storage errors. It also solves some of the known cross-browser incompatibilities and differences between specification and browser behavior.

Currently, browsers need to include a snapshot of cookies in every HTTP request, leading to cookie changes being propagated across the storage and network stacks. Modern browsers have highly optimized cookie store implementations, but we’ll never be able to make cookies as efficient as the other storage mechanisms, which don’t need to talk to the network stack. This proposal however, does not aim to change how cookies are handled at the network layer or the security model of cookies in general

For more detailed information about the Cookie Store API, you can check the proposal document/draft here, the explainer material, and also the GitHub repo. Details about the cookie change event can be found here. Furthermore, you can also compare this proposal with an alternative implementation, a simple, lightweight JavaScript API for handling browser cookies.

: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

.
Alexander Nnakwue Software engineer. React, Node.js, Python, and other developer tools and libraries.

Leave a Reply