As a frontend developer, it’s pretty common to deal with Cross Origin Resource Sharing, or CORS.
In the past, I have pretty thoughtlessly used
* configuration on my backend servers to allow
I’ve never taken more than a cursory look at how it all works.
Last week, I learned that I had been making some assumptions that were not true (and were not immediately clear to me from reading the MDN documentation).
What I Learned
I learned that it is possible for a server to respond to a preflight
OPTIONS request with all the
appropriate headers that whitelist the real request, but then respond to the real request with
a different set of headers, and thoroughly confuse everyone.
The big reveal is that an
OPTIONS request isn’t coupled to the real CORS request at all. It is
the browser that maintains the connection between them.
This seems obvious in retrospect, because a stateless server will not have any way to connect an
OPTIONS request to the followup real request, so it has no obligation to respond to a CORS
request in a way that matches its response to the preflight request.
My Understanding Before I Learned This
Previously, I thought that the entire purpose of an
OPTIONS request was to enable a CORS request.
I assumed that if an
OPTIONS request came back with the appropriate headers, the CORS request
would be whitelisted and all would be well.
But consider this scenario:
- The browser sends a preflight request to the server
The server responds with 200 OK and the following headers:
HTTP/1.1 200 0K Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET Access-Control-Allow-Headers: * Access-Control-Expose-Headers: *
The browser sends the GET request
The server responds with a 200 and the following headers:
HTTP/1.1 200 OK Access-Control-Allow-Methods: GET Access-Control-Allow-Headers: * Access-Control-Expose-Headers: *
Notice that the
Access-Control-Allow-Origin header is missing from the GET request.
This means that although the preflight request told the browser that the CORS
GET was okay
to send, the server “changed it’s mind” about it on the real
GET request. This is uncommon and
most likely a server bug, but it is possible.
Until now, I thought that the CORS related response headers on the
GET request didn’t matter!
I assumed that if the
OPTIONS request permitted the
GET, then all was ok.
To understand this more, I read the MDN documentation on CORS (which is really good), and learned that not all CORS requests require a preflight request. These are called “simple” requests.
A good followup question from this nugget information would have been: if the
OPTIONS request is
responsible for notifying the browser about CORS rules maintained by the server, how do simple
requests get those rules?
This makes it easier to debunk my working understanding of CORS. The rules of CORS are PER request,
OPTIONS request is simply a way for browsers to abort requests prematurely.