Same Origin Policy And What Cookies Cant Protect
Origin is a three-part match: protocol, host, and port. All three exist because the browser's first attempt at sharing across sites, document.domain, was a security hole waiting to happen.
The last post closed out cookie tampering with signing and real sessions. There's one boundary left that decides which requests even get to carry those cookies in the first place, and it's the rule behind every CORS error you've ever been annoyed by: the same-origin policy.
Origin Is Three Things, Not One
An origin is a tuple of three parts: protocol, host, and port. All three have to match for two URLs to be considered the same origin. Change any single one, and the browser treats them as completely unrelated sites, no matter how similar they look.
ExpandTwo matching origins on protocol, host, and port next to three mismatched examples, each blocked by a single field being different.
A few things that feel like they should count as "the same site" but don't:
http://app.example.comandhttps://app.example.comdiffer only in protocol, and that's enough to be a different originhttps://app.example.comandhttps://api.example.comdiffer in host, since host includes the full subdomainhttps://app.example.comandhttps://app.example.com:8080differ in port, even with everything else identical
Any one of those mismatches is enough on its own. This is the rule a browser is enforcing every time a fetch call from your local dev server gets blocked trying to reach an API on a different port.
The Web Almost Didn't Have This Rule
The same-origin policy arrived around 2001, which means there was a real stretch of early web history where browsers had things like AJAX-style requests without this protection in place at all. For essentially all of modern web development since, though, the same-origin policy has simply always been there, quietly deciding what a script is allowed to touch.
document.domain: A Good Idea That Fell Apart Instantly
Before CORS existed as the accepted answer, developers reached for whatever workaround was available. JSONP abused the fact that <script> tags ignore the same-origin policy entirely, and some relied on WebSocket connections instead. The one worth understanding in detail, because it shows exactly why an opt-in trust mechanism can backfire, was document.domain. A page could set its own domain value, opting itself into sharing an origin with another site under the same parent domain.
document.domain = "example.com";The idea: app.example.com and dashboard.example.com could each opt into example.com, and the browser would treat them as the same origin from then on. It sounds reasonable for exactly as long as it takes to ask the obvious follow-up question: who else could do that?
Anyone. Any site anywhere, sharing that same parent domain, could set the identical value and opt itself into the same origin. A mechanism meant for one company's own subdomains to cooperate turned into a mechanism any co-tenant on a shared domain could use to reach into your origin too. It's the same shape of problem as the leading-dot cookie domain from a couple posts back, a convenience with a blast radius nobody sized correctly.
CORS Is the Deliberate Replacement
It's tempting to treat CORS as an annoyance, something standing between your frontend and the API you're trying to call. Reframed against what it replaced, CORS is closer to a reasonable, deliberate opt-in mechanism than a free-for-all like document.domain ever was.
Instead of one page unilaterally deciding to share its origin with an unbounded set of others, CORS puts the decision on the server being called. The server explicitly lists which origins are allowed, and the browser enforces that list on the client's behalf. That's a narrower, safer version of the exact cooperation document.domain was trying to provide, minus the part where anyone could opt in without permission.
What This Boundary Doesn't Cover
Same-origin policy decides which sites can read responses from which other sites. It has nothing to do with what already-loaded JavaScript, running on your own page, is allowed to do with cookies sitting right there in the browser.
That gap is worth closing the loop on, because it's easy to walk away from this series thinking cookie attributes are a complete defense. They are not, not on their own. A cookie can be HttpOnly, Secure, and signed, everything covered across this entire series, and still end up in an attacker's hands if that attacker manages to get their own JavaScript running on your page.
HttpOnly blocks reading the cookie directly. It does nothing to stop a script from making a request as the logged-in user, using the cookie automatically attached to that request, without ever touching document.cookie at all. That's a different attack shape, and it's exactly what cross-site scripting and cross-site request forgery are, both covered later in this course.
What This Series Actually Covered
Every post in this chapter chipped away at one question: how does the browser fake a persistent identity on top of a protocol that forgets everything, and where does that fake identity actually break.
- Cookies exist because HTTP has no memory of its own
- Attributes like domain and path decide how far a cookie's reach extends
- A cookie's value means nothing if the server trusts it without proof
- HttpOnly and sessions separate what's exposed to the client from what's real
- Signing detects tampering without ever hiding the value
- Same-origin policy decides who's even allowed to make the request in the first place
None of these are a single fix. That's the layered thinking this whole series opened with: stacked together, they're a real defense, with the honest caveat that a page running attacker-controlled JavaScript can still slip past all of them.
That caveat is not a loose thread to worry about yet. It's the deliberate opening for the next chapter of this course.
The Essentials
- An origin is protocol, host, and port together. Any single mismatch means a different origin, full stop.
- The same-origin policy has existed since roughly 2001, and has been a constant in web development for essentially its entire modern history.
document.domainlet a page unilaterally opt into sharing an origin, and any other site on the same parent domain could do the exact same thing.- CORS is the deliberate, permissioned replacement, with the server explicitly choosing which origins to trust instead of leaving it wide open.
- Same-origin policy governs cross-site requests, not what a page's own JavaScript can do. A cookie can be perfectly configured and still leak if attacker-controlled script runs on the page.
- Cookie attributes are layers, not a complete defense. The remaining gap is exactly where XSS and CSRF live.
Further Reading and Watching
- What is CORS | CORS Explained by example: a walkthrough of same-origin policy and how CORS headers grant a deliberate exception to it.
- Same-origin policy: MDN's full reference on how origin is defined and what it does and doesn't restrict.
Keep reading