Origins, DNS and CORS
The parts of a URL, how DNS finds a host, what an origin and a site are, and how CORS lets a server share responses across origins, each with a live demo.
The browser decides what a page may do by where it came from. A URL names a host; DNS turns the host into an address; the scheme, host and port together form the page's origin, and the browser keeps origins apart unless a server explicitly shares with another one through CORS. This page builds that model step by step; the DNS lookups and cross-origin requests in the demos are real. It follows Networking.
URLs, hosts and domains
A URL is an address with fixed parts: scheme://user@host:port/path?query#fragment. Many bugs come from mixing up the words for its middle.
- Hostname is the name (
shop.example.com); host adds the port when one is written (shop.example.com:8443); the browser sends it in theHostheader so one server can serve many sites. - Domain names read right to left: top-level domain (
.com,.uk), the registered name (example), then any subdomains (shop.). The public suffix is the part anyone can register under (com,co.uk,github.io). - The port picks the program on the machine; 443 for https and 80 for http are implied when not written.
- The fragment (
#…) stays in the browser and is never sent. Build URLs withnew URL()andURLSearchParamsso values are encoded correctly.
Try it: take a URL apart
Try:
- Scheme
- httpshow to talk to the server
- Hostname
- shop.example.comthe name DNS looks up
- Port
- 8443
- Host
- shop.example.com:8443hostname + port if written; sent in the Host header
- Path
- /cart
- Query
- item = 42, size = m
- Fragment
- reviewsnever sent to the server
Origin = scheme + host + port
https://shop.example.com:8443
Site = scheme + registrable domain (simplified)
https://example.com
Do: Parse and build URLs with
new URL(path, base)andurl.searchParams.set().Avoid: Concatenating strings or splitting on "/" and "?": encoding and edge cases break.
Read more:MDN: What is a URL?(opens in a new tab)MDN: What is a domain name?(opens in a new tab)MDN: URL API(opens in a new tab)
DNS: from a name to an address
Computers connect to IP addresses, not names. The Domain Name System is a distributed, cached directory that answers "what is the address of this name?", usually in a few milliseconds because someone nearby already asked.
- Your device asks a recursive resolver (from your ISP, or 1.1.1.1, 8.8.8.8). If it hasn't cached the answer, it asks a root server, then the top-level domain's servers, then the domain's own authoritative name servers.
- Record types:
A/AAAA(IPv4 / IPv6 address),CNAME(an alias for another name, common with CDNs),MX(mail),TXT(verification, SPF),NS(who is authoritative). - Every record has a TTL: how many seconds caches may keep it. Lower it before moving servers, or old addresses linger.
- A lookup is the first round trip of a new origin;
dns-prefetchandpreconnectstart it early. DNS over HTTPS hides your lookups from the network.
Try it: look up a real name
Try:
Measured: this site's server asked its resolver
Look up a name to see its records.
- How a resolver finds it (illustration)
- 1Browser and OS cacheSeen recently? Answer at once.
- 2Recursive resolver (ISP, 1.1.1.1, 8.8.8.8)Has it cached? Otherwise it asks on your behalf:
- 3Root server“Who handles .com?” → the .com servers
- 4.com TLD server“Who handles example.com?” → example.com's name servers
- 5example.com name server (authoritative)“example.com?” → the record, with a TTL
- 6Back down the chainEach cache keeps the answer for its TTL.
Do: Keep critical files on few origins and use a DNS provider with servers near your users.
Avoid: Changing a record with a long TTL and expecting everyone to see it at once.
Read more:MDN: DNS(opens in a new tab)
Origins, sites and the same-origin policy
An origin is scheme + host + port: https://shop.example.com and https://shop.example.com:8443 are different origins. The same-origin policy keeps a page from reading anything that belongs to another origin.
- Without it, any site you visit could read your webmail through your logged-in cookies. So the browser lets a page send requests and embed other origins (images, scripts, iframes, form posts), but not read their responses or DOM.
- A site is looser: scheme + registrable domain (
https://example.comcoversapp.andapi.). Cookies are scoped by site and domain, not origin;SameSite=LaxorStrictkeeps them off cross-site requests, which blocks most CSRF. - Windows of different origins talk with
postMessage, always checkingevent.origin. - Local development has the same rules:
localhost:3000andlocalhost:4000are different origins.
Try it: compare two URLs
- Can'tread a fetch response unless the server allows it with CORS
- Can'treach into the other window's DOM or storage (iframes, popups); use postMessage
- SendsSameSite=Lax/Strict cookies on requests between them
Do: Serve the frontend and its API from the same origin (or a proxy path like /api) when you can: no CORS needed.
Avoid: Assuming subdomains are "the same": app.example.com can't read api.example.com without CORS.
Read more:MDN: Same-origin policy(opens in a new tab)web.dev: Same-site and same-origin(opens in a new tab)
CORS: letting other origins read a response
Cross-Origin Resource Sharing is how a server tells the browser "this other origin may read my response". It loosens the same-origin policy; it is not a security feature for the server, and a CORS error is the browser protecting the user.
- The browser adds an
Originheader; the server answers withAccess-Control-Allow-Originnaming that origin (or*). No match: the response is hidden from the page, but the request was still sent and handled. - Simple requests (GET, HEAD, POST with a form or text body and no custom headers) go straight out. Anything else first sends a preflight: an
OPTIONSrequest asking about the method and headers, answered withAccess-Control-Allow-Methods/-Headers;Access-Control-Max-Agecaches the answer. - Credentials (cookies, auth) need
credentials: "include"on the request andAccess-Control-Allow-Credentials: trueplus the exact origin (never*) on the response. - Fix CORS on the server (or with a same-origin proxy), not in the browser:
mode: "no-cors"only gives you an unreadable, opaque response.
Try it: send a real cross-origin request
The page (origin null) sends
Needs a preflight: Content-Type: application/json isn't one of the three simple types.
The server answers with
Do: Keep an allow-list of origins on the server, echo the matching one, and send Vary: Origin.
Avoid: Reflecting any
Originwith credentials allowed, or allowingnull(sandboxed and file pages send it): every site could then read your users' data.
Read more:MDN: CORS(opens in a new tab)MDN: Preflight request(opens in a new tab)web.dev: Cross-origin resource sharing(opens in a new tab)