URL Origin and CORS Security
Cross-Origin Resource Sharing (CORS) is the browser security mechanism that governs whether a web page can make requests to a different origin. Understanding how browsers determine "same-origin" — and how CORS policies allow or block cross-origin requests — is essential for any developer building APIs, SPAs, or microservice architectures.
What Is an Origin
An origin is defined by three URL components: scheme, host, and port.
https://api.example.com:443/data
↑ ↑ ↑
scheme host port
Two URLs share the same origin if and only if all three components match exactly.
| URL A | URL B | Same Origin? | Reason |
|---|---|---|---|
https://example.com/a | https://example.com/b | Yes | Same scheme, host, port |
https://example.com | http://example.com | No | Different scheme |
https://example.com | https://api.example.com | No | Different host |
https://example.com | https://example.com:8080 | No | Different port |
https://example.com | https://example.com:443 | Yes | 443 is default for HTTPS |
The path, query string, and fragment do not affect origin. /a and /b on the same origin are same-origin by definition.
The Same-Origin Policy
The same-origin policy (SOP) is the default rule: a script running on one origin cannot read responses from a different origin. This prevents malicious sites from stealing data from authenticated sessions on other sites.
Without SOP, if you visited evil.com, its JavaScript could:
- Make a fetch to
bank.com/api/balance(your browser includes your bank cookies) - Read the response
- Exfiltrate your balance to the attacker
SOP blocks step 2 — the script on evil.com cannot read the response from bank.com because the origins differ.
What SOP Allows
SOP does not block all cross-origin traffic. It specifically blocks reading cross-origin responses. The following are still allowed:
- Cross-origin navigations: clicking a link or setting
window.location - Cross-origin form submissions: posting form data to another origin
- Embedded resources:
<img>,<script>,<link>,<video>tags loading cross-origin content - Cross-origin writes:
POSTandPUTrequests viafetchorXMLHttpRequest(the request is sent, but the response cannot be read)
This distinction is crucial: SOP prevents data theft, not data sending. CSRF attacks exploit the "writes allowed" gap.
How CORS Works
CORS is a relaxation of SOP — a way for servers to explicitly allow specific origins to read responses. It uses HTTP headers to negotiate access.
Simple Requests
A "simple" request (GET, HEAD, or POST with limited headers) is sent directly. The browser adds an Origin header:
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://frontend.example.com
The server responds with CORS headers:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Credentials: true
Content-Type: application/json
If Access-Control-Allow-Origin matches the requesting origin (or is *), the browser allows the script to read the response. If the header is missing or does not match, the browser blocks the response.
Preflight Requests
For non-simple requests (custom headers, PUT/DELETE methods, JSON content type), the browser sends a preflight OPTIONS request first:
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://frontend.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, X-Auth-Token
The server responds with allowed methods and headers:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, X-Auth-Token
Access-Control-Max-Age: 86400
Only after the preflight succeeds does the browser send the actual request.
Common CORS Mistakes
Wildcard with Credentials
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
This combination is invalid. Browsers reject it because * with credentials would allow any site to make authenticated requests. If you need credentials, you must specify the exact origin.
Reflecting the Origin Header Naively
Some servers echo back whatever Origin header they receive:
# Dangerous!
response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin')
This effectively disables SOP — any origin can read responses. Always validate the origin against an allowlist before reflecting it.
Missing Preflight Handling
If your API does not respond to OPTIONS requests, preflight checks fail, and browsers block the actual request. Ensure your server or API gateway handles OPTIONS for all CORS-enabled routes.
Split Stack Anti-Pattern
When the frontend and API run on different origins (e.g., app.example.com and api.example.com), every API call is cross-origin. This is the most common source of CORS issues. Solutions:
- Same-origin proxy: serve the API behind the same origin using a reverse proxy (Nginx, Cloudflare Workers)
- Subdomain CORS: allow
https://app.example.comexplicitly on the API server - Cookie domain: set cookies on
.example.comto share auth across subdomains
Key Takeaways
- Origin is defined by scheme + host + port — path and query do not matter.
- The same-origin policy blocks reading cross-origin responses, not sending cross-origin requests.
- CORS headers let servers opt in to cross-origin reads; without them, browsers block responses.
- Never combine
Access-Control-Allow-Origin: *withAccess-Control-Allow-Credentials: true. - Always validate the
Originheader against an allowlist — never blindly reflect it.
Try It Yourself
Inspecting URL components to debug a CORS issue? The URL Parser dissects any URL into scheme, host, port, path, and query — so you can quickly identify origin mismatches.