SyntaxStudy
Sign Up
Web Security Same-Origin Policy and Why CORS Exists
Web Security Beginner 1 min read

Same-Origin Policy and Why CORS Exists

The Same-Origin Policy (SOP) is a browser security mechanism that prevents JavaScript on one origin from reading responses from a different origin. An origin is the combination of protocol, hostname, and port — `https://api.example.com:443` and `https://app.example.com:443` are different origins. Without SOP, a malicious page could use `fetch()` to read the victim's bank statement, email inbox, or any other resource the browser can reach with the victim's cookies. SOP is very restrictive, which is a problem for legitimate modern web architectures where a front-end SPA at `app.example.com` needs to call an API at `api.example.com`. Cross-Origin Resource Sharing (CORS) is the official mechanism that allows servers to selectively relax SOP by including `Access-Control` response headers that tell the browser which cross-origin requests are permitted. CORS is enforced entirely by the browser — it does not protect server-to-server communication. CORS has two request categories. "Simple" requests (GET, POST with specific content types) are sent immediately; the browser checks the `Access-Control-Allow-Origin` response header and either exposes the response to JavaScript or blocks it. "Complex" requests (DELETE, PUT, custom headers, JSON content-type) are preceded by a preflight OPTIONS request. The browser sends the preflight to ask if the actual request is permitted; only if the server responds with appropriate Access-Control headers does the browser proceed.
Example
# Illustrating CORS preflight with curl

# 1. Preflight request (browser sends this automatically before complex requests)
curl -i -X OPTIONS https://api.example.com/users \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: DELETE" \
  -H "Access-Control-Request-Headers: Authorization, Content-Type"

# Expected preflight response (server must include these headers):
# HTTP/2 204
# Access-Control-Allow-Origin: https://app.example.com
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
# Access-Control-Allow-Headers: Authorization, Content-Type
# Access-Control-Max-Age: 86400
# Vary: Origin

# 2. Actual DELETE request (browser sends after successful preflight)
curl -i -X DELETE https://api.example.com/users/42 \
  -H "Origin: https://app.example.com" \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json"

# 3. Credentialed request (cookies + CORS)
# Browser only sends cookies cross-origin if:
#   a) fetch() uses credentials: 'include'
#   b) Server responds with Access-Control-Allow-Credentials: true
#   c) Access-Control-Allow-Origin is a specific origin (not '*')
curl -i -X GET https://api.example.com/profile \
  -H "Origin: https://app.example.com" \
  --cookie "session=abc123"
# Response must have:
# Access-Control-Allow-Origin: https://app.example.com  (NOT *)
# Access-Control-Allow-Credentials: true