Fetch API bug: can’t GET or SET multiple Set-Cookie Headers A nasty issue affecting the Fetch API's Headers interface cripples the pratical usage of the whole framework for some HTTP-related tasks: bug or bad design?

Fetch API bug: can't GET or SET multiple Set-Cookie Headers

Fetch is a new native JavaScript API that provides an interface for fetching resources (including across the network): according to Google Developers Documentation, “Fetch makes it easier to make web requests and handle responses than with the older XMLHttpRequest“, thus applying to be the spiritual successor of the XHR-based approach (widely used by JQuery and the likes).

As a matter of fact, Fetch API has been designed to be very familiar to anyone who has used XMLHttpRequest, while providing a more powerful and flexible feature set. The new API provides a generic definition of Request and Response objects, as well as other interface involved with network requests: such approach allow them to be used wherever they are needed in the future, whether it’s for service workers, Cache API, and other similar things that handle or modify requests and responses, or any kind of use case that might require you to generate your responses programmatically. It also defines related concepts such as Cross-Origin Resource Sharing (CORS) and the HTTP Origin header semantics, supplanting their separate definitions elsewhere.

I’ve personally used Fetch API in most of my latest JavaScript projects, such as CORSflare – a pure JS Proxy specifically designed to overcome CORS-related and SameOrigin-based issues: however, today I’ve stumbled upon a major bug that, for the first time, led me to doubt that such interface is mature enough for production usage.

The issue

In a nutshell, the bug affects the Fetch API’s Headers interface, which seems to be unable to properly process HTTP responses containing multiple Set-Cookie  headers, thus resulting in a cookie nightmare for the client.

To properly understand what I’m talking about, here’s the full story.

As you might know, RFC 6265 indicates that it is allowed to have multiple headers with the Set-Cookie name: that’s quite an obvious choice, since it allows to set multiple cookies in a single HTTP response.

Unfortunately, Fetch API doesn’t allow to do that because all the methods exposed by its Headers interface (including get(), set(), append(), entries() and all the rest) have been implemented to merge the values of all the headers with the same name into a single header separated by commas.

For example, if we do this:

and then we try to read the set-cookie values using get('set-cookie') , or by iterating the headers variable using entries() , we get this result:

It’s worth noting that the same wrong behaviour also happens if we try to read or manipulate an existing response object having multiple headers with the same name (i.e. created by other frameworks that arguably support such allowed behavior): in other words, it seems like the Fetch API is completely unable to properly deal with such scenario.

Now, while this behavior is desired for some headers, such as Accept, the Set-Cookie header is not parsed correctly by most browsers (including Chrome and Firefox), thus resulting in cookies not being correctly set.

Bug or bad design?

The first time I’ve stumbled upon this issue, I honestly thought it was a bug: however, by carefully reading the Fetch API documentation for the various methods, I’ve started to think that the whole scenario was worked as intended. For example, here’s what the append() method’s documentation actually says:

The append() method of the Headers interface appends a new value onto an existing header inside a Headers object, or adds the header if it does not already exist. The difference between set() and append() is that if the specified header already exists and accepts multiple values, set() will overwrite the existing value with the new one, whereas append() will append the new value onto the end of the set of values.

And also, later on:

If the specified header already exists, append() will change its value to the specified value. If the specified header already exists and accepts multiple values, append() will append the new value to the end of the value set.

The append() intended behaviour is then shown in the following code sample:

The same code sample is also used within the get() method’s documentation, which enforces the same behaviour:

If the header has multiple values associated with it, the byte string will contain all the values, in the order they were added to the Headers object.

To put it in other words: instead of append a new Set-Cookie key/value pair (as required by RFC 6265) and retrieve them as a key/value array, the Fetch API seems to be handling those headers using a standard key/value container that only supports a one-dimentional set of unique keys.

StackOverflow (hopefully) to the rescue

Being an avid StackExchange user, I often visit those sites (StackOverflow above all) to answer the questions related to the softwares, operating systems, development frameworks and programming languages I use for work and hobby. This time I chose to ask a question myself, hoping to find a workaround to solve this nasty Fetch API problem. Here’s the link to my question:

Let’s see if I’ll be able to get a valid answer to fix my issue for good.

Conclusions

That’s it, at least for the time being: if I’ll find a way to overcome this nasty Fetch API issue, I won’t fail to let you know by updating this article or writing a new post: take care and happy development!

 

About Ryan

IT Project Manager, Web Interface Architect and Lead Developer for many high-traffic web sites & services hosted in Italy and Europe. Since 2010 it's also a lead designer for many App and games for Android, iOS and Windows Phone mobile devices for a number of italian companies. Microsoft MVP for Development Technologies since 2018.

View all posts by Ryan

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.