title: Safari rejects redirected CORS request due to browser-set headers
body: <h2>Summary</h2>
  
  <p>Safari rejects some CORS requests which involve redirects, claiming that some header is not allowed. But that header is never requested by the script, but added by the browser, so I think it should not matter.</p>
  
  <ul>
  <li>Is Safari's behavior a bug,</li>
  <li>is there a problem with the specs, or</li>
  <li>is there a reason for things to be like this?</li>
  </ul>
  
  <h2>Error indicates some header is not allowed in CORS</h2>
  
  <p>I'm observing situations where Safari appears to interpret the CORS protocol more strictly than other browsers. It rejects certain requests with the error message</p>
  
  <blockquote>
    <p><strong>[Error]</strong> Failed to load resource: Request header field … is not allowed by <code>Access-Control-Allow-Headers</code>.</p>
  </blockquote>
  
  <p>I've seen this for the header field <code>Accept-Encoding</code>, and after adding that field to <code>Access-Control-Allow-Headers</code> the same error was repeated with <code>DNT</code>. Other fields included in the <code>Access-Control-Request-Headers</code> list of the preflight request are <code>Cache-Control</code>, <code>Origin</code> and <code>Accept-Language</code>.</p>
  
  <p>As far as I can tell, this only affects requests which get redirected. In cases where I managed to immediately name the final location, the request succeeded.</p>
  
  <h2>WHATWG XHR: Request should have no author-set headers</h2>
  
  <p>I created the <code>XMLHttpRequest</code> in my own code, without any library or framework interfering with it. I'm absolutely certain <strong>I never called the <a href="https://xhr.spec.whatwg.org/#the-setrequestheader()-method" rel="nofollow noreferrer"><code>setRequestHeader()</code></a> method.</strong></p>
  
  <p>Following the <a href="https://xhr.spec.whatwg.org/" rel="nofollow noreferrer">WHATWG XHR spec</a> I would therefore assume that my <a href="https://xhr.spec.whatwg.org/#author-request-headers" rel="nofollow noreferrer">author request headers</a> list should be empty. The <a href="https://xhr.spec.whatwg.org/#request" rel="nofollow noreferrer">Request</a> section states:</p>
  
  <blockquote>
    <p>The <a href="https://xhr.spec.whatwg.org/#author-request-headers" rel="nofollow noreferrer">author request headers</a> is an initially empty <a href="https://fetch.spec.whatwg.org/#concept-header-list" rel="nofollow noreferrer">header list</a>.</p>
  </blockquote>
  
  <p>I can read in the documentation that a non-empty argument to <a href="https://xhr.spec.whatwg.org/#dom-xmlhttprequest-send" rel="nofollow noreferrer"><code>send()</code></a> might trigger <code>Content-Type</code> as an author request header, but I'm experiencing problems even with such a no-argument call.</p>
  
  <h2>W3C CORS: Only author-set headers should matter</h2>
  
  <p>The <a href="https://www.w3.org/TR/cors/" rel="nofollow noreferrer">W3C CORS recommendation</a> describes <a href="https://www.w3.org/TR/2014/REC-cors-20140116/#cross-origin-request-with-preflight-0" rel="nofollow noreferrer">the <code>Access-Control-Request-Headers</code> field</a> as being composed of author request headers:</p>
  
  <blockquote>
    <p>If <a href="https://www.w3.org/TR/2014/REC-cors-20140116/#author-request-headers" rel="nofollow noreferrer">author request headers</a> is not empty include an <code>Access-Control-Request-Headers</code> header with as header field value a comma-separated list of the header field names from <a href="https://www.w3.org/TR/2014/REC-cors-20140116/#author-request-headers" rel="nofollow noreferrer">author request headers</a> in lexicographical order, each <a href="https://www.w3.org/TR/2014/REC-cors-20140116/#converted-to-ascii-lowercase" rel="nofollow noreferrer">converted to ASCII lowercase</a> (even when one or more are a <a href="https://www.w3.org/TR/2014/REC-cors-20140116/#simple-header" rel="nofollow noreferrer">simple header</a>).</p>
  </blockquote>
  
  <p>And the likely reason for an error that I can see, according to that spec, would be this:</p>
  
  <blockquote>
    <p>If the field name of each header in <a href="https://www.w3.org/TR/2014/REC-cors-20140116/#author-request-headers" rel="nofollow noreferrer">author request headers</a> is not an <a href="https://www.w3.org/TR/2014/REC-cors-20140116/#ascii-case-insensitive" rel="nofollow noreferrer">ASCII case-insensitive</a> match for one of the header field names in <em>headers</em> and the header is not a <a href="https://www.w3.org/TR/2014/REC-cors-20140116/#simple-header" rel="nofollow noreferrer">simple header</a>, apply the <a href="https://www.w3.org/TR/2014/REC-cors-20140116/#cache-and-network-error-steps" rel="nofollow noreferrer">cache and network error steps</a>.</p>
  </blockquote>
  
  <h2>WHATWG Fetch takes the list from XHR</h2>
  
  <p>Perhaps the W3C CORS recommendation is the wrong place to look. Perhaps it has been superseded by the <a href="https://fetch.spec.whatwg.org/" rel="nofollow noreferrer">WHATWG Fetch living standard</a>? That writes in its section about <a href="https://fetch.spec.whatwg.org/#cors-preflight-fetch" rel="nofollow noreferrer">CORS-preflight fetch</a></p>
  
  <blockquote>
    <ol start="3">
    <li><p>Let <em>headers</em> be the <a href="https://fetch.spec.whatwg.org/#concept-header-name" rel="nofollow noreferrer">names</a> of <em>request</em>'s <a href="https://fetch.spec.whatwg.org/#concept-request-header-list" rel="nofollow noreferrer">header list</a>'s <a href="https://fetch.spec.whatwg.org/#concept-header" rel="nofollow noreferrer">headers</a>, excluding <a href="https://fetch.spec.whatwg.org/#cors-safelisted-request-header" rel="nofollow noreferrer">CORS-safelisted request-headers</a> and duplicates, sorted lexicographically, and <a href="https://fetch.spec.whatwg.org/#byte-lowercase" rel="nofollow noreferrer">byte-lowercased</a>.</p></li>
    <li><p>Let <em>value</em> be the items in <em>headers</em> separated from each other by 0x2C.</p></li>
    <li><p><a href="https://fetch.spec.whatwg.org/#concept-header-list-set" rel="nofollow noreferrer">Set</a> <a href="https://fetch.spec.whatwg.org/#http-access-control-request-headers" rel="nofollow noreferrer"><code>Access-Control-Request-Headers</code></a> to <em>value</em> in <em>preflight</em>'s <a href="https://fetch.spec.whatwg.org/#concept-request-header-list" rel="nofollow noreferrer">header list</a>.</p></li>
    </ol>
  </blockquote>
  
  <p>So what is the header list here? The connection between the living standards for <a href="https://xhr.spec.whatwg.org/" rel="nofollow noreferrer">XHR</a> and <a href="https://fetch.spec.whatwg.org/" rel="nofollow noreferrer">Fetch</a> appears to be <a href="https://xhr.spec.whatwg.org/#dom-xmlhttprequest-send" rel="nofollow noreferrer">the <code>send()</code> method specification</a>.</p>
  
  <blockquote>
    <ol start="6">
    <li><p>Let <em>req</em> be a new <a href="https://fetch.spec.whatwg.org/#concept-request" rel="nofollow noreferrer">request</a>, initialized as follows:</p>
    
    <ul>
    <li><strong><a href="https://fetch.spec.whatwg.org/#concept-request-method" rel="nofollow noreferrer">method</a>:</strong> <a href="https://xhr.spec.whatwg.org/#request-method" rel="nofollow noreferrer">request method</a></li>
    <li><strong><a href="https://fetch.spec.whatwg.org/#concept-request-url" rel="nofollow noreferrer">url</a>:</strong> <a href="https://xhr.spec.whatwg.org/#request-url" rel="nofollow noreferrer">request URL</a></li>
    <li><strong><a href="https://fetch.spec.whatwg.org/#concept-request-header-list" rel="nofollow noreferrer">header list</a>:</strong> <a href="https://xhr.spec.whatwg.org/#author-request-headers" rel="nofollow noreferrer">author request headers</a></li>
    <li><strong><a href="https://fetch.spec.whatwg.org/#unsafe-request-flag" rel="nofollow noreferrer">unsafe-request flag</a>:</strong> Set. </li>
    <li>[…]</li>
    </ul></li>
    </ol>
  </blockquote>
  
  <p>So it binds the author request headers (which according to my considerations above should be empty) to the header list which is used to build the list of requested headers. At least initially.</p>
  
  <h2>Headers might get added</h2>
  
  <p>Of course, the Fetch specification is long, and mentions the term “header list” in <em>many</em> places. It might well be that there is some clause somewhere in there which mandates adding certain header fields. But I see no portion where <code>Accept-Encoding</code> or <code>DNT</code> are explicitely mentioned as intended additions. They are listed as <a href="https://fetch.spec.whatwg.org/#forbidden-header-name" rel="nofollow noreferrer">forbidden headers</a> over which an author should have no control.</p>
  
  <p>However, the following bit of text from the <a href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch" rel="nofollow noreferrer">HTTP-network-or-cache fetch </a> section mentions them in a note:</p>
  
  <blockquote>
    <ol start="13">
    <li><p>Modify <em>httpRequest</em>'s <a href="https://fetch.spec.whatwg.org/#concept-request-header-list" rel="nofollow noreferrer">header list</a> per HTTP.</p>
    
    <blockquote>
      <p><em>Note: It would be great if we could make this more normative somehow. At this point <a href="https://fetch.spec.whatwg.org/#concept-header" rel="nofollow noreferrer">headers</a> such as <code>Accept-Encoding</code>, <code>Connection</code>, <code>DNT</code>, and <code>Host</code>, are to be <a href="https://fetch.spec.whatwg.org/#concept-header-list-append" rel="nofollow noreferrer">appended</a> if necessary.</em></p>
    </blockquote>
    
    <p><code>Accept</code>, <code>Accept-Charset</code>, and <code>Accept-Language</code> must not be included at this point.</p>
    
    <blockquote>
      <p><em>Note: <code>Accept</code> and <code>Accept-Language</code> are already included (unless <code>fetch()</code> is used, which does not include the latter by default), and <code>Accept-Charset</code> is a waste of bytes. See <a href="https://fetch.spec.whatwg.org/#http-header-layer-division" rel="nofollow noreferrer">HTTP header layer division</a> for more details.</em></p>
    </blockquote></li>
    </ol>
  </blockquote>
  
  <p>I find it very hard to judge the scope of this addition. Is this “adding headers per HTTP” the cause for extra headers being considered for CORS, in addition to those specified by the auther? But why only in case of redirects? The <a href="https://fetch.spec.whatwg.org/#concept-http-redirect-fetch" rel="nofollow noreferrer">HTTP-redirect fetch</a> section does not mention adding any more headers to the list of request headers.</p>
  
  <h2>Observed behavior would be bad</h2>
  
  <p>If the behavior I observe were according to the spec, then my main concern would be that the it would essentially prevent the addition of new and useful HTTP headers. You'd never know which sites might suddenly get broken as browsers start to not only add them but also include them in CORS checks. On the other hand, with only author-created scripts being considered, it would be sufficient to match request creation library to server implementation, with no worries about what browsers do. Sounds like a much saner setup to me.</p>
  
  <h2>Question</h2>
  
  <p>So I repeat my main question:</p>
  
  <ul>
  <li>Is Safari's behavior a bug,</li>
  <li>is there a problem with the specs, or</li>
  <li>is there a reason for things to be like this?</li>
  </ul>
  
  <h2>Workaround</h2>
  
  <p>For one server I just solved this by <a href="https://serverfault.com/a/799764/129921">configuring my Apache</a> to <a href="https://stackoverflow.com/a/13147554/1468366">return all headers</a>, but that's more of a workaround. I'd like to really understand what Safari is doing here, and why.</p>
  
  <h2>Cross links</h2>
  
  <p>There are several questions on SO indicating that Safari has problems with the combination of CORS and redirects:</p>
  
  <ul>
  <li><p><a href="https://stackoverflow.com/q/15093281/1468366">Safari Ajax cors request not following redirect</a> (2013)<br>
  Accepted answer states that “Apple claims that this is the way the HTML spec is written” without citing a source. Sounds like <a href="https://bugs.webkit.org/show_bug.cgi?id=112471#c2" rel="nofollow noreferrer">Webkit bug 112471 comment 2</a> except that answer predates the comment.</p></li>
  <li><p><a href="https://stackoverflow.com/q/24833312/1468366">HTML5 CORS request fails in safari after redirect</a> (2014)<br>
  Accepted self-answer proposes a server-assisted workaround.</p></li>
  <li><p><a href="https://stackoverflow.com/q/32332919/1468366">Safari fails CORS request after 302 redirect</a> (2015)<br>
  Unaccepted answer mentions webkit <a href="https://bugs.webkit.org/show_bug.cgi?id=98838" rel="nofollow noreferrer">bug 98838</a> (unconfirmed) which in turn suggests <a href="https://bugs.webkit.org/show_bug.cgi?id=112471" rel="nofollow noreferrer">bug 112471</a> (recently fixed). That one at some point also mentions <a href="https://bugs.webkit.org/show_bug.cgi?id=63460" rel="nofollow noreferrer">bug 63460</a> titled “CORS should only deal with request headers set by script authors”, but doesn't seem to reach a definite consensus on this. Towards the end the <code>DNT</code> flag is explicitely named.</p></li>
  <li><p><a href="https://stackoverflow.com/q/30757449/1468366">Safari turns Simple Cors Request into Preflight after 302 redirect</a> (2015)<br>
  Unanswered.</p></li>
  </ul>
  
  <p>The list of similar questions on the right might provide further information.</p>