Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't the browser reuse the authorization headers after an authenticated XMLHttpRequest?

I'm developing Single Page App using Angular. The backend exposes REST services that require Basic authentication. Getting index.html or any of the scripts does not require authentication.

I have an odd situation where one of my view has a <img> where the src is the url of a REST API that requires authentication. The <img> is processed by the browser and I have no chance to set the authorization header for GET request it makes. That causes the browser to prompt for credentials.

I attempted to fix this by doing this:

  1. Leave img src empty in the source
  2. At "document ready", make an XMLHttpRequest to a service (/api/login) with the Authorization header, just to cause the authentication to occur.
  3. Upon completing that call, set the img src attribute, thinking that by then, the browser would know to include the Authorization header in subsequent requests...

...but it doesn't. The request for the image goes out without the headers. If I enter the credentials, then all other images on the page are right. (I've also tried and Angular's ng-src but that produced the same result)

I have two questions:

  1. Why didn't the browser (IE10) include the headers in all requests after a successful XMLHttpRequest?
  2. What can I do to work around this problem?

@bergi asked for requests' details. Here they are.

Request to /api/login

GET https://myserver/dev30281_WebServices/api/login HTTP/1.1 Accept: */* Authorization: Basic <header here> Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729) Connection: Keep-Alive 

Response (/api/login)

HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Length: 4 Content-Type: application/json; charset=utf-8 Expires: -1 Server: Microsoft-IIS/8.0 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET Date: Fri, 20 Dec 2013 14:44:52 GMT 

Request to /user/picture/2218:

GET https://myserver/dev30281_WebServices/api/user/picture/2218 HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729) Connection: Keep-Alive 

And then the web browser prompts for credentials. If I enter them, I get this response:

HTTP/1.1 200 OK Cache-Control: public, max-age=60 Content-Length: 3119 Content-Type: image/png Server: Microsoft-IIS/8.0 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET Date: Fri, 20 Dec 2013 14:50:17 GMT 
like image 994
Sylvain Avatar asked Dec 16 '13 18:12

Sylvain


People also ask

Does browser automatically send Authorization header?

discussion. Only types like Basic , NTLM of Authorization header is sent automatically by browser in following cases: The Authorization header field allows a user agent to authenticate itself with an origin server – usually, but not necessarily, after receiving a 401 (Unauthorized) response.

Can you have more than one Authorization header?

A sender MUST NOT generate multiple header fields with the same field name in a message unless either the entire field value for that header field is defined as a comma-separated list [i.e., #(values)] or the header field is a well-known exception (as noted below).

How do I pass the Authorization header in GET request?

To send a GET request with a Bearer Token authorization header, you need to make an HTTP GET request and provide your Bearer Token with the Authorization: Bearer {token} HTTP header.


2 Answers

The google drive uploader is created using angular js. Its authors faced a similar problem. The icons were hosted on a different domain and putting them as img src= violated the CSP. So, like you, they had to fetch the icon images using XHR and then somehow manage to get them into the img tags.

They describe how they solved it. After fetching the image using XHR, they write it to the HTML5 local file system. They put its URL on the local file system in the img's src attribute using the ng-src directive.

$http.get(doc.icon, {responseType: 'blob'}).success(function(blob) {   console.log('Fetched icon via XHR');   blob.name = doc.iconFilename; // Add icon filename to blob.   writeFile(blob); // Write is async, but that's ok.   doc.icon = window.URL.createObjectURL(blob);   ... } 

As for the why, I don't know. I assume that creating a session token for retrieving the images is out of the question? I'd expect that Cookie headers do get sent? Is it a cross-origin request? In that case, do you set the withCredentials property? Is it a P3P thing perhaps?

like image 21
flup Avatar answered Sep 19 '22 22:09

flup


Basic idea

Load the images via JavaScript and display them on the site. The advantage is that the authentication credentials will never find their way into the HTML. They will resist at the JavaScript side.

Step 1: load the image data via JS

That's basic AJAX functionality (see also XMLHttpRequest::open(method, uri, async, user, pw)):

var xhr = new XMLHttpRequest(); xhr.open("GET", "your-server-path-to-image", true, "username", "password");  xhr.onload = function(evt) {   if (this.status == 200) {     // ...   } }; 

Step 2: format the data

Now, how can we display the image data? When using HTML, one would normally assign an URI to the src attribute of the image element. We can apply the same principle here except for the fact that we use data URIs instead of 'normal' http(s):// derivates.

xhr.onload = function(evt) {   if (this.status == 200) {     var b64 = utf8_to_b64(this.responseText);     var dataUri = 'data:image/png;base64,' + b64; // Assuming a PNG image      myImgElement.src = dataUri;   } };  // From MDN: // https://developer.mozilla.org/en-US/docs/Web/API/window.btoa function utf8_to_b64( str ) {     return window.btoa(unescape(encodeURIComponent( str ))); } 

Canvas

There is also another option which consists in painting the loaded data in a <canvas> field. This way, the user won't be able to right-click the image (the area where the canvas is positioned) as opposed to the <img> and data URIs where the user will see a long data URI when viewing the image properties panel.

like image 88
ComFreek Avatar answered Sep 21 '22 22:09

ComFreek