An apparent defect in how IE handled our OAuth consent flow at work turned out to be an interesting difference in how Firefox and IE (Firefox 6 and IE8, in this case) handle content loaded via a 302 HTTP response.
After one of our UI developers beautified our OAuth consent page (where the user authorizes a client application to use data on his or her behalf), one of our testers pointed out that the overall flow stopped working in Internet Explorer. It still worked in Firefox, though, and she told us that if she permanently authorized the client in Firefox, the flow still worked in Internet Explorer.
We allow OAuth clients to provide an image that will be displayed when we ask users for consent. However, it’s not required, and so occasionally on the consent page the IMG tag would have an empty SRC attribute: <img src="" alt="client Logo"/>.

When the SRC attribute is empty, the browser loads the page URI instead. (So, it’s a self-reference, like the . entry on a filesystem.) This is where the difference between Firefox and IE comes in.
When a user accesses our site, he or she needs an OAuth access token for the site to work, and so Spring issues a 302 redirect from whatever URI was originally being accessed to the consent page. For example, if the user requests / (the home page), they’ll be redirected to /consentToUseOfData?oauth_token={tokenId}. (In the background, Spring obtains an OAuth request token from the OAuth provider, and replaces {tokenId} with the request token.)
As a result, the URI for the page had two wrinkles. The original URI for the page being loaded was /, but this was temporarily redirected to /consentToUseOfData?oauth_token={tokenId}. When the self-referring IMG tag is encountered, IE uses the original URI while Firefox uses the redirected URI.
While everyone loves to hate on Internet Explorer, initially I didn’t disagree with their approach here. The HTTP spec says, Since the redirection might be altered on occasion, the client SHOULD continue to use the Request-URI for future requests.
I thought the request triggered by the IMG should be considered a “future request,” meaning Internet Explorer’s behavior would be correct.
However, I did a little more digging. The HTML4 spec for IMG (The choice of HTML4 was admittedly arbitrary, since we’re using the essentially spec-less HTML5.) says the SRC attribute is a URI: Relative URIs are resolved to full URIs using a base URI. … For more information about base URIs, please consult the section on base URIs in the chapter on links.
The relevant section states,
12.4.1 Resolving relative URIs
User agents must calculate the base URI for resolving relative URIs according to [RFC1808], section 3. The following describes how [RFC1808] applies specifically to HTML.
User agents must calculate the base URI according to the following precedences (highest priority to lowest):
- The base URI is set by the
BASEelement.- The base URI is given by meta data discovered during a protocol interaction, such as an HTTP header (see [RFC2616]).
- By default, the base URI is that of the current document. Not all HTML documents have a base URI (e.g., a valid HTML document may appear in an email and may not be designated by a URI). Such HTML documents are considered erroneous if they contain relative URIs and rely on a default base URI.
We define no BASE element, nor does the response contain a Content-Location header (per RFC2616, this is how HTTP defines a Base URI), so the following section of RFC1808 applies:
3.3. Base URL from the Retrieval URL
If no base URL is embedded and the document is not encapsulated within some other entity (e.g., the top level of a composite entity), then, if a URL was used to retrieve the base document, that URL shall be considered the base URL. Note that if the retrieval was the result of a redirected request, the last URL used (i.e., that which resulted in he actual retrieval of the document) is the base URL.[emphasis added]
So, as usual, Firefox appears to be following the relevant spec and Internet Explorer is not. This one is pretty esoteric, though, so I’m tempted to forgive them.
Ultimately, we resolved the problem by removing the IMG tag when no image is provided. The fact that it was loading a broken image was a defect itself, of course.
GET https://serverName/ HTTP/1.1
Host: serverName
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Cookie: <snip>
HTTP/1.1 302 Moved Temporarily
Date: Thu, 01 Sep 2011 07:30:59 GMT
Server: Apache-Coyote/1.1
Location: https://serverName/consentToUseOfData?oauth_token=3760ff95-fd5c-4dcc-870f-a9d9c7717509
Content-Length: 0
Set-Cookie: JSESSIONID=1891371460FB24565FECFDC3C598E06A; Path=/
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/plain
This is the initial request for the homepage. A key point is the Accept header, which is clearly requesting HTML content.
GET https://serverName/consentToUseOfData?oauth_token=3760ff95-fd5c-4dcc-870f-a9d9c7717509 HTTP/1.1
Host: serverName
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Cookie: <snip>
HTTP/1.1 200 OK
Date: Thu, 01 Sep 2011 07:31:01 GMT
Server: Apache-Coyote/1.1
Pragma: no-cache
Cache-Control: no-cache
Cache-Control: no-store
Expires: Mon, 01 Jan 2001 10:00:00 GMT
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Transfer-Encoding: chunked
<snip>
The redirected request. Again, the Accept header is clearly requesting HTML content. Also, note the lack of a Referrer header.
GET https://serverName/consentToUseOfData?oauth_token=3760ff95-fd5c-4dcc-870f-a9d9c7717509 HTTP/1.1
Host: serverName
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0
Accept: image/png,image/*;q=0.8,*/*;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: https://serverName/consentToUseOfData?oauth_token=3760ff95-fd5c-4dcc-870f-a9d9c7717509
Cookie: <snip>
HTTP/1.1 200 OK
Date: Thu, 01 Sep 2011 07:31:03 GMT
Server: Apache-Coyote/1.1
Pragma: no-cache
Cache-Control: no-cache
Cache-Control: no-store
Expires: Mon, 01 Jan 2001 10:00:00 GMT
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Keep-Alive: timeout=5, max=96
Connection: Keep-Alive
Transfer-Encoding: chunked
The second request for /consentToUseOfData, notice that Firefox is requesting an image. This was the first indication (after about an hour of debugging, disabling scripts, pouring over logs, etc.) that the broken image might be the cause of the problem. The Referrer header is present here, too, and refers to the second request. Finally, note that the OAuth token in the request is the same as the original OAuth token.
GET https://serverName/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
Accept-Encoding: gzip, deflate
Host: serverName
Connection: Keep-Alive
Cookie: <snip>
HTTP/1.1 302 Moved Temporarily
Date: Thu, 01 Sep 2011 06:05:48 GMT
Server: Apache-Coyote/1.1
Location: https://serverName/consentToUseOfData?oauth_token=af1f9e74-2a6e-4aa5-9fcf-cc2d385cc3a3
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/plain
The initial request for the homepage. While Internet Explorer is not as requesting HTML as clearly as Firefox did, the Accept header here is typical of an IE page load.
GET https://serverName/consentToUseOfData?oauth_token=af1f9e74-2a6e-4aa5-9fcf-cc2d385cc3a3 HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
Accept-Encoding: gzip, deflate
Host: serverName
Connection: Keep-Alive
Cookie: <snip>
HTTP/1.1 200 OK
Date: Thu, 01 Sep 2011 06:05:48 GMT
Server: Apache-Coyote/1.1
Pragma: no-cache
Cache-Control: no-cache
Cache-Control: no-store
Expires: Mon, 01 Jan 2001 10:00:00 GMT
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Transfer-Encoding: chunked
fd8
<snip>GET https://serverName/ HTTP/1.1
Accept: */*
Referer: https://serverName/consentToUseOfData?oauth_token=af1f9e74-2a6e-4aa5-9fcf-cc2d385cc3a3
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
Accept-Encoding: gzip, deflate
Host: serverName
Connection: Keep-Alive
Cookie: <snip>
HTTP/1.1 302 Moved Temporarily
Date: Thu, 01 Sep 2011 06:05:49 GMT
Server: Apache-Coyote/1.1
Location: https://serverName/consentToUseOfData?oauth_token=e725bf13-9153-4f75-a412-b995de8aed99
Content-Length: 0
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Content-Type: text/plain
The second request, but Internet Explorer requests the original URI, not the redirected URI. The Accept header isn’t as specific as it was before, an indication that this request came from an IMG tag. Notice too that the OAuth token in the response is different from the OAuth token in the request’s Referrer.
Because this request didn’t include an OAuth token parameter, Spring’s OAuth client obtained a new request token from the OAuth provider and stored that new token in the user’s session. That means that when the user submits their approval using the HTML form from the first /consentToUseOfData, there is a mismatch between the submitted token value and the value in the user’s session.
GET https://serverName/consentToUseOfData?oauth_token=e725bf13-9153-4f75-a412-b995de8aed99 HTTP/1.1
Accept: */*
Referer: https://serverName/consentToUseOfData?oauth_token=af1f9e74-2a6e-4aa5-9fcf-cc2d385cc3a3
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
Accept-Encoding: gzip, deflate
Host: serverName
Connection: Keep-Alive
Cookie: <snip>
HTTP/1.1 200 OK
Date: Thu, 01 Sep 2011 06:05:49 GMT
Server: Apache-Coyote/1.1
Pragma: no-cache
Cache-Control: no-cache
Cache-Control: no-store
Expires: Mon, 01 Jan 2001 10:00:00 GMT
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Keep-Alive: timeout=5, max=97
Connection: Keep-Alive
Content-Length: 8683
<snip>The second request for /consentToUseOfData, this time with a different OAuth token.
Comments
Post new comment