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?
Question&Answers:
os