As I develop a JavaScript client to be integrated into third-party websites, similar to the Facebook Like button, I encounter a challenge. The client needs to access information from an API that requires basic HTTP authentication. Here is how the setup is simplified:
A snippet on a third-party site's page looks like this:
<script
async="true"
id="web-dev-widget"
data-public-key="pUbl1c_ap1k3y"
src="http://web.dev/widget.js">
</script>
widget.js makes a call to 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
Please note that the Access-Control-Allow-Origin
header matches the Origin
instead of using a wildcard due to the use of a credentialed request (withCredentials
).
All components are in place for an asynchronous cross-domain authenticated request. This works well in Chrome 25 on OS X 10.8.2, where network requests are visible and responses are as expected.
In Firefox 19, however, no network requests show up in Firebug for the API, and an error message stating
NS_ERROR_DOM_BAD_URI: Access to restricted URI denied
is logged in the console.
After further investigation, I discovered that Gecko doesn't permit usernames and passwords in cross-site URLs, as mentioned in the comments on this Stack Overflow post. To address this limitation, I attempted another method of sending authenticated requests by Base64 encoding the credentials and including them 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 approach resulted in a 401 Unauthorized
response to the OPTIONS
request, leading me to question why it functions correctly in Chrome but not in Firefox. How can I ensure consistent sending and receiving of the OPTIONS
request?