71

I am writing a JavaScript client to be included on 3rd party sites (think Facebook Like button). It needs to retrieve information from an API that requires basic HTTP authentication. The simplified setup looks like this:

A 3rd party site includes this snippet on their page:

<script 
async="true"
id="web-dev-widget"
data-public-key="pUbl1c_ap1k3y"
src="http://web.dev/widget.js">
</script>

widget.js calls the API:

var el = document.getElementById('web-dev-widget'),
    user = 'token',
    pass = el.getAttribute('data-public-key'),
    url = 'https://api.dev/',
    httpRequest = new XMLHttpRequest(),
    handler = function() {
      if (httpRequest.readyState === 4) {
        if (httpRequest.status === 200) {
          console.log(httpRequest.responseText);
        } else {
          console.log('There was a problem with the request.', httpRequest);
        }
      }
    };

httpRequest.open('GET', url, true, user, pass);
httpRequest.onreadystatechange = handler;
httpRequest.withCredentials = true;
httpRequest.send();

The API has been configured to respond with appropriate headers:

Header set Access-Control-Allow-Credentials: true
Header set Access-Control-Allow-Methods: "GET, OPTIONS"
Header set Access-Control-Allow-Headers: "origin, authorization, accept"
SetEnvIf Origin "http(s)?://(.+?\.[a-z]{3})$" AccessControlAllowOrigin=$0
Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin

Note that the Access-Control-Allow-Origin is set to the Origin instead of using a wildcard because I am sending a credentialed request (withCredentials).

Everything is now in place to make an asynchronous cross-domain authenticated request, and it works great in Chrome 25 on OS X 10.8.2. In Dev Tools, I can see the network request for the OPTIONS request before the GET request, and the response comes back as expected.

When testing in Firefox 19, no network requests appear in Firebug to the API, and this error is logged in the console: NS_ERROR_DOM_BAD_URI: Access to restricted URI denied

After much digging, I found that Gecko doesn't allow the username and password to be directly in a cross-site URI according to the comments. I assumed this was from using the optional user and password params to open() so I tried the other method of making authenticated requests which is to Base64 encode the credentials and send in an Authorization header:

// Base64 from http://www.webtoolkit.info/javascript-base64.html
auth = "Basic " + Base64.encode(user + ":" + pass);

...
// after open() and before send()
httpRequest.setRequestHeader('Authorization', auth);

This results in a 401 Unauthorized response to the OPTIONS request which lead to Google searches like, "Why does this work in Chrome and not Firefox!?" That's when I knew I was in trouble.

Why does it work in Chrome and not Firefox? How can I get the OPTIONS request to send and respond consistently?

Community
  • 1
  • 1
maxbeatty
  • 9,050
  • 3
  • 33
  • 36

3 Answers3

145

Why does it work in Chrome and not Firefox?

The W3 spec for CORS preflight requests clearly states that user credentials should be excluded. There is a bug in Chrome and WebKit where OPTIONS requests returning a status of 401 still send the subsequent request.

Firefox has a related bug filed that ends with a link to the W3 public webapps mailing list asking for the CORS spec to be changed to allow authentication headers to be sent on the OPTIONS request at the benefit of IIS users. Basically, they are waiting for those servers to be obsoleted.

How can I get the OPTIONS request to send and respond consistently?

Simply have the server (API in this example) respond to OPTIONS requests without requiring authentication.

Kinvey did a good job expanding on this while also linking to an issue of the Twitter API outlining the catch-22 problem of this exact scenario interestingly a couple weeks before any of the browser issues were filed.

whme
  • 4,908
  • 5
  • 15
  • 28
maxbeatty
  • 9,050
  • 3
  • 33
  • 36
  • 2
    Should I provide always the same response to OPTIONS request or should it depend on the resource requested? If it depends on the resource, the attacker can use the OPTIONS request to discover server content/urls and features supported by that resources. Please also see this question: http://stackoverflow.com/questions/20805058/options-request-authentication – IT Hit WebDAV Dec 27 '13 at 17:46
  • 29
    Is there any security risk of not authenticating OPTION requests? – Kevin Meredith May 16 '14 at 03:53
  • 14
    For those ending up here: it's worth using `curl -X OPTIONS http://yourdomain.example.com` to see what your server is responding with for an OPTIONS request. Also ensure you completely wipe the browser cache, Firefox aggressively caches these responses so even though you've changed the server config it still reports the same errors. – toxaq Jul 13 '14 at 10:27
  • This has been such a difficult discovery process for me. I'm not sure why it took so long to find this answer but knowing about "block cookies flag" and that it applies to "pre-flight" has helped me understand that *OPTIONS requests will not send cookies*. – Corey Alix Apr 30 '15 at 16:06
  • My OPTIONS response is completely empty. Is this correct? What should it respond with? – nnyby Jan 12 '16 at 03:12
  • @nnyby I believe an empty response body should be ok. The response status needs to be 200 or similarly successful – maxbeatty Jan 12 '16 at 17:07
  • 1
    I would really like an answer to the question @KevinMeredith asked... What are the security risks, if any, of not requiring authentication for OPTIONS requests? – heez Mar 14 '18 at 17:47
  • 1. I believe the link is now incorrect, and should be https://fetch.spec.whatwg.org/#cors-preflight-fetch. 2. Is this still true? The spec doesn't seem to mention excluding authentication for preflight requests. – coderMe Oct 16 '18 at 16:03
  • @heez - To my understanding you are just reducing your security as you are not denying requests outright and instead allowing the cross origin requests to be made to your server. Assuming your security on the actual requests is solid then it should not be a problem other than the added traffic from people making requests that are bound to fail because the pre-flight request could not deny them. – J Z Sep 23 '19 at 20:58
  • @codeME I believe that typically speaking an Authorization header is not going to be sent in a pre-flight request though it might also be browser specific. According to FF docs at https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request the pre-flight request will _only_ contain certain headers: `[The pre-flight request is] an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method, Access-Control-Request-Headers, and the Origin header.` – J Z Sep 23 '19 at 21:08
  • **"There is a bug in Chrome"** OK, we like such bugs, never fix it please ;) – a55 Dec 03 '20 at 16:42
  • You put it as if only IIS users face this issue. Also, it'd be nice if you could quote the relevant parts of the specification, and explain it if need be (the terminology) for those who don't usually take a peek at it. – x-yuri Aug 25 '22 at 06:38
10

This is an old post but maybe this could help people to complete the CORS problem. To complete the basic authorization problem you should avoid authorization for OPTIONS requests in your server. This is an Apache configuration example. Just add something like this in your VirtualHost or Location.

<LimitExcept OPTIONS>
    AuthType Basic
    AuthName <AUTH_NAME>
    Require valid-user
    AuthUserFile <FILE_PATH>
</LimitExcept>
  • this is the single really good answer -- thank you !!!!! --- sugest--- SetEnvIf Origin "^(.*?)$" origin_is=$0 Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is – bortunac Jul 28 '20 at 15:34
0

It was particular for me. I am sending a header named 'SESSIONHASH'. No problem for Chrome and Opera, but Firefox also wants this header in the list "Access-Control-Allow-Headers". Otherwise, Firefox will throw the CORS error.

bitkorn
  • 628
  • 1
  • 8
  • 22