CORS Lite
Every time I see a CORS error, I have to rebuild my mental model of how CORS works. Maybe I don’t see CORS errors often enough, or maybe I’m just getting old. For whatever reason, I just can’t retain that model. But… every single time I try to rebuild it, I get it wrong. This blog post is for me – something to refer back to the next time I’m wondering what CORS is.
There are lots of docs you can read online if you want a reference. This is not a reference; this is the “Lite” version.
What is CORS?
Actually, one of those online references mentioned above has a perfectly fine definition:
“Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.”
My Error(s)
My first problem is one of terminology. I always think of server as the URL I typed into my browser. In fact, that is the origin. The server is the resource server from which the page (loaded from the origin) requests resources.
With that clarified, we can state my true problem using the same language as the definition:
I gravitate towards the understanding that it is the origin that tells my browser which resource servers it trusts.
This seems natural to me. Shouldn’t it be the page I am visiting that determines which pages it trusts to load content from? Nope.
Why I’m Wrong
To understand why I’m wrong, it helps to use concrete examples – instead of the abstract “origin A loads resources from server B” types of examples.
Let’s say you do your banking with (origin) https://www.mybank.example.com. They’ve got a modern website: a JavaScript frontend app that makes calls to a web service backend. The frontend authenticates you, stores your credentials in a cookie, and sends that cookie with every request it makes to the backend. One of those backend services is /transfer_to/<routing_number>/<account_number>/<amount>.
Actually, that’s not quite accurate. Since your credentials are in a cookie, the frontend app doesn’t actively send them. It relies on the browser to send the cookie with every XMLHttpRequest it makes to the backend web services. That’s an important distinction: if your browser stored a cookie for www.mybank.example.com, it will send that cookie for every request it makes to www.mybank.example.com.
So let’s say you go to your bank, log in, and use their bill payment system to send a rent check to your landlord. Having taken care of that, it’s time to crack open a beer and watch a movie. So, after opening that beer, you go to your favorite source of online content, https://www.shadymovietorrenting.example.com.
You never logged out of your bank’s website though. So the credentials in the cookie are still there, and they’re still valid…
And guess what? When you open https://www.shadymovietorrenting.example.com, it makes this XMLHttpRequest:
https://www.mybank.example.com/transfer_to/555/1234/10000
Your browser happily attaches the cookie to the request, your bank accepts the (valid) credentials, and you’re out $10,000.
The Correct Model
The number of websites (servers) that you trust is much smaller than the number of websites (origins) that you navigate to in your web browser. For those servers that you do trust, CORS allows them to tell your browser which origins they allow to access their resources.
The good news is that your browser’s default policy is “same-origin only”. That’s why the example I gave above never happens in the wild.
Disclaimer: I created the first few drafts of this post before asking an LLM to be my editor. There were em dashes and semicolons in the original – that’s just how I write. You can believe me; or not. I don’t care ;-)
-
2025-12-19
CORS Lite -
2025-11-28
F#: There was always an Option -
2024-06-25
Spring @Cacheable -
2023-12-03
Lisp Macros -
2022-12-31
Happy New Year