Web applications used to be written as monoliths, the server and client intermingled. ASP.NET, PHP were typical of the type. There was no firm API between the presentation layer and backend. They were updated together. Gradually abstractions came in like Model View Controller, but the monolith remained.
More recently people have started writing applications where the browser is a richer first class citizen. Platforms like Angular, React are typical of the type. As these became popular the older ‘Form/Submit’ model gave way to the Single Page Application. The browser gained the logic to make it richer, more reactive to the user, and, the backend shrunk to being a mere data gateway.
And then we had an epiphany: why make any dedicated backend at all? Instead, have each application, running in the Browser, directly access a set of RESTful API’s. Great. Now we can evolve microservices without worrying about presentation. We can evolve presentation without worrying about data. And life was good.
But, like all good things in life, risks became real and crooks became rich. Flaws were found. And, like all such things, standards emerged to codify and solidify best practices. And, for the direct-consuming-api-application, we invented Cross-Origin Resource Sharing (called CORS).
CORS is a mechanism that uses additional HTTP headers to instruct the browser (and thus the web application) what ORIGIN(s) it may use, and how to access resources from a different ORIGIN. This is important since, once we broke up the monolith, the application is now fetched from a different ORIGIN than the data. We want to protect against evil JavaScript posting the secrets home to a command-and-control server. We want to allow the good application to fetch your profile. And CORS is how we do that.
Once upon a time the same-origin policy was sufficient: where you came from was where you could talk. Now we need more.
The first thing to be aware of is there is a new flow (called pre-flight), which uses an HTTP OPTIONS request. When your application (and thus your browser) first wants to use some new API it does a pre-flight check. If this fails, it assumes it is because of CORS, and logs this (but no more details). JavaScript is unable to tell why this failed so that it cannot fall back to other methods.
Now, CORS is very complicated to get right. You can indicate if authentication is allowed (to prevent credentials from being leaked), you can indicate who can talk where. As a consequence there is fixed set of headers which are sent for CORS, and yours may not be in the list! A server must manually indicate if it wishes to allow authentication (or any header other than the standard set).
Unlike its cousin the Content-Security-Policy, CORS is very simplistic in hosts. You get 1 or all. So you can say “example.com” or “*”. This can be challenging if you want a small set of origins to use your API. And by challenging I mean “write some complex logic on your server out scope of this presentation”.
Do you need this? If you write an API you must implement CORS, you have no choice. Make it so!
If you want to see how to use the Vary: Origin header to allow two applications in the same browser to work against the same API backend, see this post.