Asynchronous Access to HTTP Cookies
The Cookie Store API is available for Origin Trials starting in Chrome 69. The API introduces the following exciting possibilities:
- Cookies can be accessed asynchronously, avoiding jank on the main thread.
- Changes to cookies can be observed, avoiding polling.
- Cookies can be accessed from service workers.
You (probably) don't need cookies
Before diving into the new API, I'd like to state that cookies are still the Web platform's worst client-side storage primitive, and should still be used as a last resort. This isn't an accident - cookies were the Web's first client-side storage mechanism, and we've learned a lot since then.
The main reasons for avoiding cookies are:
Cookies bring your storage schema into the your backend API. Each HTTP request carries a snapshot of the cookie jar. This makes it easy for backend engineers to introduce dependencies on the current cookie format. Once this happens, your frontend can't change its storage schema without deploying a matching change to the backend.
Cookies have a complex security model. Modern Web platform features follow the same origin policy, meaning that each application gets its own sandbox, and is completely independent from other applications that the user might be running. Cookie scopes make for a significantly more complex security story, and merely attempting to summarize that would double the size of this article.
Cookies have high performance costs. Browsers need to include a snapshot of your cookies in every HTTP request, so every change to cookies must be 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.
For all the reasons above, modern Web applications should avoid cookies and instead store a session identifier into IndexedDB, and explicitly add the identifier to the header or body of specific HTTP requests, via the fetch API.
That being said, you're still reading this article because you have a good reason to use cookies...
Say goodbye to jank
The venerable
document.cookie
API is a fairly guaranteed source of jank for your application. For example,
whenever you use the document.cookie
getter, the browser has to stop executing
JavaScript until it has the cookie information you requested. This can take a
process hop or a disk read, and will cause your UI to jank.
A straightforward fix for this problem is switching from the document.cookie
getter to the asynchronous Cookie Store API.
await cookieStore.get('session_id')
// {
// domain: "example.com",
// expires: 1593745721000,
// name: "sesion_id",
// path: "/",
// sameSite: "unrestricted",
// secure: true,
// value: "yxlgco2xtqb.ly25tv3tkb8"
// }
The document.cookie
setter can be replaced in a similar manner. Keep in mind
that the change is only guaranteed to be applied after the Promise returned by
cookieStore.set
resolves.
await cookieStore.set({ name: 'opt_out', value: '1' });
// undefined
Observe, don't poll
A popular application for accessing cokies from JavaScript is detecting when
the user logs out, and updating the UI. This is currently done by polling
document.cookie
, which introduces jank and has a negative impact on battery
life.
The Cookie Store API brings an alternative method for observing cookie changes, which does not require polling.
cookieStore.addEventListener('change', (event) => {
for (const cookie in event.changed) {
if (cookie.name === 'session_id')
sessionCookieChanged(cookie.value);
}
for (const cookie in event.deleted) {
if (cookie.name === 'session_id')
sessionCookieChanged(null);
}
});
Welcome service workers
Because of synchronous design, the document.cookie
API has not been made
available to
service workers.
The Cookie Store API is asynchronous, and therefore is allowed in service
workers.
Interacting with the cookies works the same way in document contexts and in service workers.
// Works in documents and service workers.
async function logOut() {
await cookieStore.delete('session_id');
}
However, observing cookie changes is a bit different in service worker. Waking up a service worker can be pretty expensive, so we have to explicitly describe the cookie changes that the worker is interested in.
In the example below, an application that uses IndexedDB to cache user data monitors changes to the session cookie, and discards the cached data when the user logs off.
// Specify the cookie changes we're interested in during the install event.
self.addEventListener('install', (event) => {
event.waitFor(async () => {
await cookieStore.subscribeToChanges([{ name: 'session_id' }]);
});
});
// Delete cached data when the user logs out.
self.addEventListener('cookiechange', (event) => {
for (const cookie of event.deleted) {
if (cookie.name === 'session_id') {
indexedDB.deleteDatabase('user_cache');
break;
}
}
});
How to enable the API
To get access to this new API on your site, please sign up for the "Cookie Store API" Origin Trial. If you just want to try it out locally, the API can be enabled on the command line:
chrome --enable-blink-features=CookieStore
Passing this flag on the command line enables the API globally in Chrome for the current session.
If you give this API a try, please let us know what you think! Please direct feedback on the API shape to the specification repository, and report implementation bugs to the CookiesAPI Blink component.
We are especially interested to learn about performance measurements and use cases beyond the ones outlined in the explainer.