Monday, October 10, 2011

Cross-Origin Resource Sharing and Authentication

Cross-Origin Resource Sharing (CORS) is a nice newish technology that allows you to throw off the shackles of JSONP. To give you a taste, it allows these things:
  • Proper error handling instead of waiting for some timeout.
  • Ability to post arbitrary amounts of data.
  • No helper libraries or crazy code required.
  • Uses standard XmlHttpRequest (or XDomainRequest for IE8+).
  • Basic Authentication (for non-IE browsers)
Feel free to ignore this paragraph, but before I proceed, I just wanted to talk at Microsoft about XDomainRequest. There was no need to introduce the entirely new XDomainRequest. You only needed to introduce restrictions on XmlHttpRequest in certain cases, just like all the other browsers do. I hope you at least add proper cross-origin support to XmlHttpRequest, if not fully deprecating XDomainRequest, in the future. Regarding XDomainRequest, I also find the lack of ability to set headers quite unfortunate. This basically means that authorization information needs to be passed as part of the core message, which forces a generic API provider to rip out the authorization from the message and reformulate it into a generic message for the normal API framework to process. Specifically, this makes it impossible to send OAuth headers. Note that while you could include the auth token in the url, this has the danger of leaking into logs.
Okay, let's get into the meat of this post. I was searching for a way to authenticate access to a REST API. Since the browsers I cared about supported authentication, I thought I would just follow the web standards and use regular authentication.
Besides being a massive pain to get going with WCF or an HttpListener, I later discovered what I thought was a serious security flaw. If you allow all domains to access the REST API (which is important) then browser caching of authentication is a serious issue. For example, you visit site A, authenticate to shared service S, and then visit malicious site B. Malicious site B accesses shared service S and since your browser has cached the credentials for S, it magically logs in and allows B to access the restricted content.
Well, it turns out the browser authors are smarter than me and they disallow authentication when allowing access to arbitrary domains. Herein lies the crux of design improvement #2 that I found myself wishing for. When you perform a request using XmlHttpRequest, you specify the credentials in the call to open(). Why couldn't the API simply not cache those credentials if accessing a CORS site with "*" for the domain restriction?
Okay, I've taken you through my journey. What's a REST API author to do for authentication? Well, since the browser is inept at managing to keep its data to itself, it seems we need to do it for it. As far as I can tell, cross-origin requests requiring authentication must pass some sort of authentication information with each request. I haven't fully explored the best way to do this, but there are a variety of schemes from signing the message to session tokens to sending the username and password as part of the request. I've been exploring the options here and I'll post more on my findings in the near future.
I'd be happy to hear if I missed anything, but I'm pretty sure I understand how lame the situation is. Well, at least it's not JSONP.

2 comments:

  1. I'm attempting to the the same thing you mentioned in this post (authenticate access to a REST API). But my attempt to use Basic Auth has been pretty unsuccessful so far :-(

    Have you made any progress on getting a cross-domain AJAx request using Basic Auth to work?

    ReplyDelete
    Replies
    1. Turns out Microsoft actually listens... to who I'm not sure, but hurray nonetheless. Better CORS support is coming to IE 10. I only hope it supports headers and cross-protocol requests (ex. http page requests https resource).

      http://msdn.microsoft.com/en-us/library/ie/hh673569(v=vs.85).aspx#cors_xhr

      Regarding basic auth, you might have some luck using .withCredentials as detailed in the XMLHttpRequest level 2 spec (reachable from above link).

      Delete