If you thought of this, I don’t blame you but I assure you that this writeup is not like the others. It won’t “just” talk about “what-and-how” of CORS but actually dive into a painful story of me facing some not-so-common issues with CORS! It will be a tale of preflight requests with hidden redirections, a ton of betrayal with browser caching, and even bugs found in server-side framework.
So, let’s go!
What is CORS?
Cross-Origin-Resource-Sharing
A fancy named “security” mechanism embedded in modern browsers like Google Chrome, Firefox, etc.
(the quotes around “security” are sarcastic which I’ll explain in the end)
Ensures that a web-page can only receive responses from resources (APIs, Backends) which it is allowed/meant to access.
“Explain to me like a 5-year old”
Kinda sad for a 5-year old to deal with CORS though lol
Say, you’re a kid. You call a store and order a 13+ movie (“accidentally” 😛). The store receives this request and delivers that movie to your house the next day.
Your dad opens the door to the delivery guy but checks the movie label and notices that it’s meant for 13+ kids, and realises you are not supposed to get that movie.
So, he throws it away and scolds you that you aren’t supposed to watch it!
“Common dude! I am not a 5-yr old, explain to me like an adult!”
Aight’, Take this example, Say, I wrote a backend API “only” for my friend’s website foo.com
I expect that any random webpage apart from foo.comshould not be able to access my API. Despite this, a webpage of a different website called unknown.com tries to make requests against my API because it’s mischievous.
When that webpage does that, the browser notices this and drops the incoming data as it realises that unknown.com doesn’t deserve to receive any data from the backend API I wrote for my friend.
Where’s the analogy with the 5-yr old kid?
You: The webpage
Your dad: The browser
The store: The backend API
The movie: Response/Data which is not meant for you but only 13+ kids.
Your dad checking the movie label beforehand: Browser applying the CORS check to the responses it receives.
Your dad scolding you instead of handing the movie to you: Browser throwing a CORS error at you instead of giving you the incoming data/response from the backend.
Enough child talk, let’s talk technical!
How does CORS “really” work?
One word: Headers! – Contains metadata about your request/response.
TL;DR of headers
As an example, I’ll open Chrome Dev Tools “Console” from my “New Tab” and make the following POST request to google.com i.e. simulating the webpage on my tab making that call to google.com
Now, I’ll dive into the Network Inspector of Chrome Dev tools to look at the skeleton of this request and response received from google.com
Notice that the request not only contains a request body but also headers. Let’s call them “Request Headers”. It holds meta-information about the Host, Browser, Cookies, Session, etc. is sent via the Request Headers.
A server like google.com can also send headers in the response back to the webpage. Via these “Response Headers”, the server sends information about the response, connection timeout, alternative protocols and even which cookies to automatically set in the browser.
So how does CORS fit into this picture?
Simple! Anytime a server sends a response back to a webpage (in your browser), the browser interferes firstly and checks the Response Headers.
It checks whether the host name of the webpage, which originated the request, is mentioned in the Access-Control-Allow-Origin section of the response headers from the backend’s response.
If it is mentioned, the browser realises this webpage deserves to talk to this API and passes the response forward to the webpage.
But if it isn’t mentioned, the browser realises that the backend is not even aware of this webpage’s website and so this webpage shouldn’t be allowed to consume that backend’s API. Therefore, the server drops the response and throws an error to the webpage saying,
Basically, what the browser is saying,
“Dude! You aren’t google.com or someone google.com trusts. You are ‘chrome-extension://pej…..’. You aren’t allowed to consume the API of google.com, I won’t give you the response”.
“chrome-extension://pej…..” is just a fancy way of saying that I made this request from my “new tab” on the browser (which is running an extension behind the scenes)
How do we fight this nasty error then?
Just don’t use a browser!
Remember, when I told that CORS is a security mechanism embedded in the modern browsers. Well, CORS is a “browser” thing!
So, If you have a simple use-case of calling a backend or an API, might as well call it from a local script or an HTTP client like POSTman.
Tell the browser you don’t care about incoming data
Sometimes, you just need to send some data to the server without caring about what it responds with. For example, your webpage wants to insert/POST an object to your backend without caring about the response. Something like “fire-and-forget”.
CORS is all about blocking “incoming data” but nothing stops from sending data from your webpage to your backend.
Therefore, your webpage, when making the request, can set mode: 'no-cors' to tell the browser that it just wants to send a request to the backend without caring about the response.
But what if I sneak into the response nonetheless 👀 You can’t lol! The response object you receive is an “opaque” object. Basically, an empty object as a placeholder for the response. This is your browser’s way of still continuing to have the CORS-protection of hiding the incoming data from you.
That’s the issue with this trick, even if your webpage is officially allowed to receive incoming data, your browser would make the response object opaque making your webpage incapable of parsing the incoming data in the response. So only use this strategy when you don’t care about the incoming response.
Make the backend tell the browser it knows you
Program the backend to add Access-Control-Allow-Origin header to all the responses it sends back. That header should contain the name of the origin (webpage, chrome-extension, etc.) which it is fine with receiving the response. With this header, the browser, upon receiving the response, realises that the response is meant for your webpage and passes it to you.
A conveniently adopted workaround is to just set Access-Control-Allow-Origin: [*] to every response from the backend. This is a simple way of the backend saying, “Anything in this world can consume the responses I send, I don’t care!”
This is the actual “solution” normally which people follow but it requires you to own the backend so that you can re-program it.
But what if you don’t own the backend? Proxy to the rescue!
With this strategy, your webpage doesn’t directly call the backend API. It instead calls a proxy (say, nginx) owned by you and the proxy forwards your request to the backend API you want to talk to.
The backend responds to the proxy. The proxy places the Access-Control-Allow-Origin: [*] header to that response and further passes it to your browser. The browser sees that header, gets happy and hands you over the response. Easy!
In the 5-yr old example, imagine your friend took the movie from the delivery boy before your dad, erased the 13+ age label from it and then, passed it to your dad. Your dad would happily hand the movie to you! Your friend is the proxy here!
Ultimately, the proxy is talking to the actual backend in a browser-less way, hence, getting the opportunity to get a readable response and forward it to your browser accordingly.
Is CORS really “secure” then?
Although I defined CORS as a “security” mechanism in the beginning (sarcastically), it is definitely not secure. After all, what a browser does behind the scenes is just make network calls which can be easily simulated via a script running in your terminal, thereby, bypassing CORS altogether.
My personal take on CORS is that it is a mere “inconvenience” for frontends so as to reduce the chance of them doing mistaken (or mischiveous) requests to un-intended backends/resources.
End note? More like intermission!
Thanks for sticking until now folks. But this ain’t the end!
This article was meant to just set the base for you and make you understand this annoying thing called “CORS”. But believe me, it gets wayyy nastier and I’ll depict that in the next blog i.e. the last blog of this series.
It will take you on a journey about a very annoying CORS issue I faced very recently and how I navigated through its currents.
It would have all the jazz and battles around: preflight requests which your browser makes, request redirection, browser’s (hidden) network cache and guess what, spotting an issue in a server-side framework 👀
The takeaways from it would be about using first-principles-thinking and leveraging your arsenal of Browser’s Dev Tools and IDE’s debugger to their limits to navigate the flooded landscape of network calls and spotting the root cause!
If you thought of this, I don’t blame you but I assure you that this writeup is not like the others.
It won’t “just” talk about “what-and-how” of CORS but actually dive into a painful story of me facing some not-so-common issues with CORS! It will be a tale of preflight requests with hidden redirections, a ton of betrayal with browser caching, and even bugs found in server-side framework.
So, let’s go!
What is CORS?
“Explain to me like a 5-year old”
Kinda sad for a 5-year old to deal with CORS though lol
Say, you’re a kid.
You call a store and order a 13+ movie (“accidentally” 😛).
The store receives this request and delivers that movie to your house the next day.
Your dad opens the door to the delivery guy but checks the movie label and notices that it’s meant for 13+ kids, and realises you are not supposed to get that movie.
So, he throws it away and scolds you that you aren’t supposed to watch it!
“Common dude! I am not a 5-yr old, explain to me like an adult!”
Aight’, Take this example,
Say, I wrote a backend API “only” for my friend’s website
foo.com
I expect that any random webpage apart from
foo.com
should not be able to access my API.Despite this, a webpage of a different website called
unknown.com
tries to make requests against my API because it’s mischievous.When that webpage does that, the browser notices this and drops the incoming data as it realises that
unknown.com
doesn’t deserve to receive any data from the backend API I wrote for my friend.Where’s the analogy with the 5-yr old kid?
The webpage
The browser
The backend API
Response/Data which is not meant for you but only 13+ kids.
Browser applying the CORS check to the responses it receives.
Browser throwing a CORS error at you instead of giving you the incoming data/response from the backend.
Enough child talk, let’s talk technical!
How does CORS “really” work?
One word: Headers!
– Contains metadata about your request/response.
TL;DR of headers
As an example, I’ll open Chrome Dev Tools “Console” from my “New Tab” and make the following POST request to google.com i.e. simulating the webpage on my tab making that call to google.com
Now, I’ll dive into the Network Inspector of Chrome Dev tools to look at the skeleton of this request and response received from google.com
So how does CORS fit into this picture?
Simple! Anytime a server sends a response back to a webpage (in your browser), the browser interferes firstly and checks the Response Headers.
It checks whether the host name of the webpage, which originated the request, is mentioned in the
Access-Control-Allow-Origin
section of the response headers from the backend’s response.If it is mentioned, the browser realises this webpage deserves to talk to this API and passes the response forward to the webpage.
But if it isn’t mentioned, the browser realises that the backend is not even aware of this webpage’s website and so this webpage shouldn’t be allowed to consume that backend’s API. Therefore, the server drops the response and throws an error to the webpage saying,
Basically, what the browser is saying,
How do we fight this nasty error then?
Just don’t use a browser!
Remember, when I told that CORS is a security mechanism embedded in the modern browsers. Well, CORS is a “browser” thing!
So, If you have a simple use-case of calling a backend or an API, might as well call it from a local script or an HTTP client like POSTman.
Tell the browser you don’t care about incoming data
Sometimes, you just need to send some data to the server without caring about what it responds with.
For example, your webpage wants to insert/POST an object to your backend without caring about the response. Something like “fire-and-forget”.
CORS is all about blocking “incoming data” but nothing stops from sending data from your webpage to your backend.
Therefore, your webpage, when making the request, can set
mode: 'no-cors'
to tell the browser that it just wants to send a request to the backend without caring about the response.But what if I sneak into the response nonetheless 👀
You can’t lol! The response object you receive is an “opaque” object. Basically, an empty object as a placeholder for the response. This is your browser’s way of still continuing to have the CORS-protection of hiding the incoming data from you.
That’s the issue with this trick, even if your webpage is officially allowed to receive incoming data, your browser would make the response object opaque making your webpage incapable of parsing the incoming data in the response. So only use this strategy when you don’t care about the incoming response.
Make the backend tell the browser it knows you
Program the backend to add
Access-Control-Allow-Origin
header to all the responses it sends back. That header should contain the name of the origin (webpage, chrome-extension, etc.) which it is fine with receiving the response. With this header, the browser, upon receiving the response, realises that the response is meant for your webpage and passes it to you.A conveniently adopted workaround is to just set
Access-Control-Allow-Origin: [*]
to every response from the backend. This is a simple way of the backend saying, “Anything in this world can consume the responses I send, I don’t care!”This is the actual “solution” normally which people follow but it requires you to own the backend so that you can re-program it.
But what if you don’t own the backend? Proxy to the rescue!
With this strategy, your webpage doesn’t directly call the backend API. It instead calls a proxy (say, nginx) owned by you and the proxy forwards your request to the backend API you want to talk to.
The backend responds to the proxy. The proxy places the
Access-Control-Allow-Origin: [*]
header to that response and further passes it to your browser.The browser sees that header, gets happy and hands you over the response. Easy!
Ultimately, the proxy is talking to the actual backend in a browser-less way, hence, getting the opportunity to get a readable response and forward it to your browser accordingly.
Is CORS really “secure” then?
Although I defined CORS as a “security” mechanism in the beginning (sarcastically), it is definitely not secure.
After all, what a browser does behind the scenes is just make network calls which can be easily simulated via a script running in your terminal, thereby, bypassing CORS altogether.
My personal take on CORS is that it is a mere “inconvenience” for frontends so as to reduce the chance of them doing mistaken (or mischiveous) requests to un-intended backends/resources.
End note? More like intermission!
Thanks for sticking until now folks. But this ain’t the end!
This article was meant to just set the base for you and make you understand this annoying thing called “CORS”. But believe me, it gets wayyy nastier and I’ll depict that in the next blog i.e. the last blog of this series.
It will take you on a journey about a very annoying CORS issue I faced very recently and how I navigated through its currents.
It would have all the jazz and battles around: preflight requests which your browser makes, request redirection, browser’s (hidden) network cache and guess what, spotting an issue in a server-side framework 👀
The takeaways from it would be about using first-principles-thinking and leveraging your arsenal of Browser’s Dev Tools and IDE’s debugger to their limits to navigate the flooded landscape of network calls and spotting the root cause!
Written by Muhammad Talha Waseem
Recent Posts
Recent Posts
Enhancing Security Testing in CI/CD Pipelines: A
The Role of Data Preprocessing in Machine
Differences Between LLM, VLM, LVM, LMM, MLLM,
Archives