Tuesday, January 3, 2017

Beyond CORS

The Problem with CORS

The App Principle: A web page is an application. In order for web pages to remain relevant in an app-driven world, web-apps must be able to do anything native apps can.

CORS breaks this principle. It restricts the information a web-app can fetch from the internet, and even if you are allowed to fetch it, it sometimes restricts what you can do with that information. Native Apps can fetch anything from the internet and do anything with it.

CORS has other problems as well. It isn’t consistently implemented across browsers. Safari 9.x interprets HTMLImage.crossOrigin differently from other browsers. CORS setup is difficult to get right and therefor it’s easy to get it wrong and actually create exactly the vulnerabilities CORS is supposed to prevent. Last, CORS returns extremely opaque errors.

Why Does CORS Exist?

I’ve been trying to understand this question for a year. My best understanding is, before CORS, web-apps were even more restricted. CORS opens up cross-domain access while preventing hacks such as:

Badwebsite.com attempts to fetch your emails from gmail.com; if you are logged in with gmail.com, without cors, it could do this!

CORS “taint” - I just don’t know. Is this a way to appease copyright holders? It doesn’t really prevent anything since Native Apps aren’t restricted in this way. Further, all you have to do is set up a basic web-proxy server and your Web-App could do anything with any image on the web. I have not been able to determine any legitimate reason for the existence of 'taint.'

CORS is the Wrong Answer

Reason #1 above is the main reason for CORS. It is legitimate from a certain perspective. There is a potential vulnerability, but it is the wrong solution. The problem is when “badwebsite.com” attempts to contact “gmail.com” it gets to freely use your cookies authenticated with gmail.com. This is something Native-Apps cannot do, so why should a web-page get to do this? And yet IT CAN - without CORS restrictions. This is insane. It’s like logging into the Gmail Native App on your phone , and then switching to MyMalicousApp and allowing it to send requests to Gmail using your login information from the Gmail app.

Beyond CORS - Sandboxed Cookies

It is relatively easy to solve the CORS problem now that we have separated the essence from the artifacts. We just need a way to allow websites to make external requests without giving them access to all the client’s cookies. The answer is to treat each webpage as a separate, isolated application with its own, sandboxed set of cookies for all the other webpages it communicates with.

I propose we add an option to XMLHttpRequest called ‘useSandboxedCookies’. When true, any web-application for a given domain is now allowed to make any request to any url - just like any native app could. However, all cookies used in those requests are local to that web application. This is exactly how native-apps work.

Example:

Alice logs into Gmail.com directly.

Then she goes to MyWebsite.com, which attempts a GET from Gmail.com

The cookies in the GET request to gmail.com are empty - the GET originated in the sandboxed cookie-space of the MyWebsite.com app.

So, even though Alice is logged into Gmail.com in one tab, in the MyWebsite.com tab, requests to Gmail.com appear to be coming from a new session.

This solution doesn’t actually do anything you can’t already do with CORS. All you need to do is set up a proxy server (example: corsproxy), which returns all the right CORS headers, and any website can fetch data from anywhere on the net, taint-free. However, setting up a proxy is not generally an option because it is costly, inefficient and not library-friendly. We need a client-side solution like the one above.

We should do away with the Taint concept entirely. It doesn't achieve anything useful, but it creates a lot of developer pain. Therefor, Sandboxed-cookie-requests should never return tainted objects.

Safe, Cross-Web-App Communication

My useSandboxedCookies proposal above solves 99% of the CORS problem. Since it could be added to the HTML spec in addition to CORS, we could stop there. However, there is another use-case that my proposal prevents but CORS allows, however poorly.

There are legitimate reasons why MyGoodWebsite might want to access Gmail.com using Alice's logged in credentials, and Gmail.com may want to allow it. For example, Gmail.com may want to offer a ‘log in with Gmail’ feature to third-party web apps. My proposal above prevents this since the only client-side code allowed to access Alice's Gmail.com cookies is Gmail.com’s code itself.

We need a reliable way for two client-side apps to communicate with each other. To do this, we can add a relatively simple extension to the postMessage API: allow postMessage to post a message to a URL. When this happens, the browser fetches data from the specified URL to initialize a headless client-side javascript session. That session can then send and respond to postMessages with other client-side web apps.

In the example above, MyGoodWebsite could postMessage to gmail.com/api.js. Once the gmail app loads, it sends back a postMessage that it is ready. Then MyGoodWebsite can use Gmail’s custom API for authentication. The gmail.com/api.js app can then use the user’s already-authenticated gmail.com cookies to communicate with the gmail.com servers and authenticate the third-party app.

Recap

I've outlined two proposals which superscede CORS and solve its problems:

Sandboxed Cookies: allow a web-app to safely fetch data from anywhere on the web with a simple client-side flag set per request, without any painful 'tainting.' Tainting is depricated.

PostMessage to URL: Allow any web-app to start any other for the purpose of safe communication and safe use of authenticated cookie credentials.

I have wasted way too many hours on CORS over the past few years, and I'm sure I'm not the only one. These proposals are simple to use and implement. They would open the web up to the web itself, instead of just native apps.

Does anyone know how to get the attention of the HTML5 standards committee?