CORS errors, same-origin policy and pre-flighted requests
Vatsal Patel / March 21, 2022
4 min read • ––– views
If you've ever built a website you'll have certainly faced a CORS error (Cross-Origin resource sharing) somewhere along the line. The browser complains in striking red color with "no access-control-allow-origin header is present on the requested resource" or "strict origin when cross origin" or "missing allow origin header" messages in the console. People, including myself, usually just implement the first answer they find on StackOverflow and forget about it ... until it gets flagged by a security team. I finally delved a bit deeper and found answers to the questions I always had about CORS and here they are.
Browsers by default block read access to response data returned from API requests (but still allow the request to be made) to another origin* due to a thing called the same-origin policy. For example, if you go to www.google.com your browser won't allow you to make a request to www.facebook.com from the browser's console. But this is a bit too restrictive for the modern age website and so CORS was added to relax this and allow certain cross-origin requests.
*An origin is identified by three things: the protocol, a hostname and a port. For example https://example.com uses the https scheme, example.com as its host and port 443 (the default for https).
Yes! Without CORS the same-origin policy would be enforced in its strictest form. By adding CORS you are relaxing the constraints and allowing chosen origins to read cross domain request responses.
The same-origin policy makes the browser safer and reduces possible attack vectors by restricting how a document or script loaded by one origin can interact with a resource from another origin. So it's a safety first kind of set up. Keep everything locked and only relax the rules through CORS if needed.
Why doesn't the same-origin policy prevent the request to be made in the first place instead of just preventing the browser to read the response?
The same-origin policy actually allows certain cross-origin writes such as links, redirects and form submission and cross-origin embedding such as images. Disabling all request would be way too restrictive and break a large part of the web.
The API needs to be configured to send back one origin in the
Access-Control-Allow-Origin header that is allowed to read the response data. You can also specify the * wildcard which allows any origin to read the response but this should largely be avoided since it completely disables the same-origin policy and removes any security benefits.
This allow-origin header only allows one value to be set. So something like
"Access-Control-Allow-Origin": "https://foo.com https://foo1.com"
isn't allowed . If you'd like your API to work for multiple origins you have to write some code in your API which obtains the origin from the request, checks it against a whitelist you've defined and then sets the request origin as the value for this response header. This would make the header's value dynamic and change it depending on the request origin, instead of one static origin value.
Some requests are treated a bit more securely and are called pre-flighted requests since they have the potential to cause damage. They require the browser to first send a HTTP OPTIONS request to find out if the actual request is safe to send. The server responds with a series of headers which tells the browser which requests are safe and which aren’t. Only then does the browser make the original request to the API.
These don't secure your API. Any bad actor can call your API via a simple curl or through apps like postman. Same origin policy only affects requests made through the browser. It's protecting the user from malicious websites trying to take advantage of the browser's "environment". Your API needs its own security in place to stop malicious actors from causing damage.
Special shout out to Danyal Khan and the rest of my colleagues at SigTech for discussing this with me and questioning my understanding!