I apologise for the rant that follows.
- User visits the client
- Client attempts to access the protected resource with an
Acceptheader to match the type of content they want back (e.g. XML or JSON)
- The server responds with a
401 Unauthorizedresponse, and a
WWW-Authenticateheader giving details of how to authenticate using OAuth2. It also includes the right
Access-Control-Allow-*headers so the client can get at the headers and make non-simple CORS requests (if the client can’t see the headers or the response, it assumes a
- The client catches the
401and sends the user off to the OAuth2 authorize endpoint
- The authorize endpoint needs the user to be authenticated, so redirects them to a login page with a query parameter so they can get back again
- The authorize endpoint then asks the user whether they want to grant permission to the client
- The user agrees, and the service sends them back to the OAuth2 return URL for the client with a token
- The client takes that token and exchanges it for access and refresh tokens using the token endpoint
- The client can then replay the original request, but this time it attaches the access token
- The API can now use the provided token to authorize the request on behalf of the user, and grants access to the resource
Simples. So what’s the problem?
Notice that in both steps 3 and 5, the remote service is presented with a request that needs the agent to be authenticated. In the first instance it’s a robot making the request, so it responds with a
401 Unauthorized. In the second it’s a human using a browser, so it responds with a
302 Found redirect to the login page. How does it tell them apart?
So far, the service has been using the
Accept header. Requests made by the client use
XMLHttpRequest, and say “XML please!”. Requests made directly by the browser have the browser-default
Accept header that says “HTML please, or whatever else you’ve got available”. It would be fair to assume that requests for HTML are being made to be shown to humans, and so a login form is most appropriate.
Internet Explorer is where this all falls down. Firstly, it doesn’t support CORS until IE10. Instead, it uses
XDomainRequest, which is intended to have feature-parity with HTML forms (e.g. only GET or PUT, can’t set or view headers). Secondly, IE8′s
Accept header doesn’t ask for HTML, but rather “I’ll have a Word document, or an Excel spreadsheet, or a JPEG image, or …, or whatever else you’ve got”.
This all means that we can’t tell the two types of request apart without doing something bespoke and non-standard. It also means that instead of being sent to the login page, the user gets a page saying “You need to authenticate” but no obvious way to do so.
It’s all a bit of a mess.