Yesterday, I started looking a site compatibility bug where a page’s layout is intermittently busted. Popping open the F12 Tools on the failing page, we see that a stylesheet is getting blocked because it lacks a CORS Access-Control-Allow-Origin response header:
We see that the client demands the header because the LINK element that references it includes a crossorigin=anonymous directive:
crossorigin="anonymous" href="//s.axs.com/axs/css/90a6f65.css?4.0.1194" type="text/css" />
Aside: It’s not clear why the site is using this directive. CORS is required to use SubResource Integrity, but this resource does not include an integrity attribute. Perhaps the goal was to save bandwidth by not sending cookies to the “s” (static content) domain?
In any case, the result is that the stylesheet sometimes fails to load as you navigate back and forward.
Looking at the network traffic, we find that the static content domain is trying to follow the best practice Include
Vary: Origin when using CORS for access control.
Unfortunately, it’s doing so in a subtly incorrect way, which you can see when diffing two request/response pairs for the stylesheet:
As you can see in the diff, the Origin token is added only to the response’s Vary directive when the request specifies an Origin header. If the request doesn’t specify an Origin, the server returns a response that lacks the Access-Control-* headers and also omits the Vary: Origin header.
That’s a problem. If the browser has the variant without the Access-Control directives in its cache, it will reuse that variant in response to a subsequent request… regardless of whether or not the subsequent request has an Origin header.
The rule here is simple: If your server makes a decision about what to return based on a what’s in a HTTP header, you need to include that header name in your
Vary, even if the request didn’t include that header.
If your response specifies
Vary: Origin to ensure that the browser rechecks with you before reusing a response, your server had better not return a
HTTP/304 Not Modified response to a request from a different Origin without *also* updating the
Access-Control-Allow-Origin to match the new request context. Otherwise, your cached response is not going to be readable in that new context.
PS: This seems to be a pretty common misconfiguration, which is mentioned in the fetch spec:
CORS protocol and HTTP caches
If CORS protocol requirements are more complicated than setting `Access-Control-Allow-Origin` to * or a static origin, `Vary` is to be used.
In particular, consider what happens if
Vary is not used and a server is configured to send
Access-Control-Allow-Origin for a certain resource only in response to a CORS request. When a user agent receives a response to a non-CORS request for that resource (for example, as the result of a navigation request), the response will lack
Access-Control-Allow-Origin and the user agent will cache that response. Then, if the user agent subsequently encounters a CORS request for the resource, it will use that cached response from the previous non-CORS request, without
Vary: Origin is used in the same scenario described above, it will cause the user agent to fetch a response that includes
Access-Control-Allow-Origin, rather than using the cached response from the previous non-CORS request that lacks
Access-Control-Allow-Origin is set to
* or a static origin for a particular resource, then configure the server to always send
Access-Control-Allow-Origin in responses for the resource — for non-CORS requests as well as CORS requests — and do not use
3 thoughts on “CORS and Vary”
Interesting… Does this do anything fun/interesting for stylesheets? I guess maybe it would allow you to enumerate the sheet’s rules?