02 - CORS Mechanics & Protocol Specification
Cross-Origin Resource Sharing (CORS) is a browser-enforced HTTP-header-based mechanism that enables backend servers to declare which external origins are authorized to read response data or execute non-simple HTTP requests.
1. CORS Request Categoriesβ
The W3C Fetch Specification categorizes cross-origin HTTP requests into three types: Simple Requests, Preflighted Requests, and Credentialed Requests.
OUTGOING CROSS-ORIGIN REQUEST
β
βββββββββββββββββββββββ΄ββββββββββββββββββββββ
βΌ βΌ
Is request method GET, HEAD, Does it contain custom
or POST with safelisted headers, PUT/DELETE,
Content-Type? or JSON payload?
β β
YES ββββββ΄βββββ NO YES ββββββ΄βββββ NO
β β β β
βΌ βΌ βΌ βΌ
βββββββββββββ βββββββββββββ βββββββββββββ βββββββββββββ
β SIMPLE β β PREFLIGHT β β PREFLIGHT β β SIMPLE β
β REQUEST β β REQUIRED β β REQUIRED β β REQUEST β
βββββββββββββ βββββββββββββ βββββββββββββ βββββββββββββ
A. Simple Requestsβ
A cross-origin request is classified as a Simple Request if it satisfies all of the following conditions:
- HTTP Method: Must be one of
GET,HEAD, orPOST. - HTTP Headers: Must only contain CORS-safelisted headers:
AcceptAccept-LanguageContent-LanguageContent-TypeRange
- Content-Type Value: If
Content-Typeis set, it must strictly be one of:application/x-www-form-urlencodedmultipart/form-datatext/plain
- No Event Listeners / ReadableStreams: No
XMLHttpRequestUploadevent listeners are registered.
Simple Request Protocol Flowβ
[!NOTE] Side Effects Warning: Even for Simple Requests, the browser transmits the HTTP request to the backend server before evaluating CORS headers. Server-side mutations triggered by
GETorPOSTrequests will execute regardless of whether the browser subsequently blocks JavaScript from reading the response.
B. Preflighted Requests (OPTIONS)β
If a request fails to meet any Simple Request criteriaβsuch as using Content-Type: application/json, sending custom headers (e.g., Authorization, X-API-Key), or using methods like PUT, DELETE, or PATCHβthe browser MUST issue an automated Preflight Request prior to sending the actual payload.
The preflight request uses the HTTP OPTIONS method to ask the target server for explicit permission to transmit the main request.
Preflighted Request Protocol Flowβ
C. Credentialed Requestsβ
By default, cross-origin requests initiated via fetch() or XMLHttpRequest do NOT transmit sensitive client credentials (such as HTTP cookies, TLS client certificates, or HTTP Basic Authentication headers).
To attach credentials to a cross-origin request, the client script must set:
- Fetch API:
credentials: 'include' - XMLHttpRequest:
xhr.withCredentials = true
The Cardinal Rule of Credentialed CORSβ
[!CAUTION] Strict Prohibition: If a client sends a credentialed request, the target server MUST NOT return a wildcard asterisk (
Access-Control-Allow-Origin: *).Browsers will strictly throw a CORS violation error and refuse to expose the response if:
Access-Control-Allow-Credentials: trueis present, ANDAccess-Control-Allow-Originis set to*.To permit credentialed cross-origin requests, the server MUST return an explicit, exact origin in the
Access-Control-Allow-Originheader (e.g.,Access-Control-Allow-Origin: https://app.example.com).
2. Complete Header Specification Matrixβ
The CORS protocol relies on two sets of HTTP headers: Request Headers (sent by the browser to specify parameters) and Response Headers (returned by the server to grant permissions).
Request Headers (Browser -> Server)β
| Header Name | Usage Scenario | Description & Example |
|---|---|---|
Origin | All CORS Requests | Indicates the origin tuple initiating the request.Origin: https://app.example.com |
Access-Control-Request-Method | Preflight OPTIONS | Informs the server which HTTP method will be used in the actual request.Access-Control-Request-Method: PUT |
Access-Control-Request-Headers | Preflight OPTIONS | Comma-separated list of custom headers the client intends to send.Access-Control-Request-Headers: Authorization, X-Correlation-ID |
Response Headers (Server -> Browser)β
| Header Name | Value Syntax | Security Impact & Description |
|---|---|---|
Access-Control-Allow-Origin | <origin> | * | Grants access to specified origin or wildcard (*). Must equal exact origin if credentials are enabled. |
Access-Control-Allow-Credentials | true | Indicates whether the browser should expose response data to JavaScript when credentials (cookies) are included. |
Access-Control-Allow-Methods | <method>, ... | List of allowed HTTP methods for preflight authorization (GET, POST, PUT, DELETE, OPTIONS). |
Access-Control-Allow-Headers | <header>, ... | List of custom headers allowed in actual request (Authorization, Content-Type, X-API-Key). |
Access-Control-Expose-Headers | <header>, ... | Whitelists response headers that frontend JavaScript is allowed to access beyond basic response headers. |
Access-Control-Max-Age | <seconds> | Maximum duration (in seconds) the browser is allowed to cache the preflight response. |
3. Exposing Custom Response Headers (Access-Control-Expose-Headers)β
By default, JavaScript executing in a cross-origin context can only access CORS-safelisted response headers:
Cache-ControlContent-LanguageContent-LengthContent-TypeExpiresLast-ModifiedPragma
If an API returns custom response headersβsuch as pagination metadata (X-Total-Count), authorization refresh tokens (X-Refresh-Token), or rate limit information (X-RateLimit-Remaining)βfrontend JavaScript CANNOT read them unless the server explicitly lists them in Access-Control-Expose-Headers.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Expose-Headers: X-Total-Count, X-Refresh-Token
X-Total-Count: 1450
X-Refresh-Token: eyJhbGciOi...
4. Modern Extensions & Edge Casesβ
A. The Necessity of the Vary: Origin Headerβ
When a backend server dynamically reflects the Origin header (or returns different CORS headers based on the caller), it MUST include the Vary: Origin HTTP response header.
[!WARNING] CDN & Cache Poisoning Risk: Without
Vary: Origin, intermediate caching proxies, CDNs (e.g., Cloudflare, Akamai), or browser HTTP caches may store a response generated for Origin A (https://app.example.com) and serve that cached response to Origin B (https://evil.com), leading to cross-site data exposure or global Denial of Service (DoS).
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
B. Private Network Access (PNA)β
Private Network Access (PNA)βformerly known as CORS-RFC1918βis a W3C specification designed to prevent public websites from pivoting into private local networks (intranets) via browser client requests.
Under PNA, when a public website (https://public-website.com) attempts to fetch resources from a private network IP address (http://192.168.1.1 or http://localhost:8080), the browser sends a special preflight request:
OPTIONS /device/status HTTP/1.1
Host: 192.168.1.1
Origin: https://public-website.com
Access-Control-Request-Private-Network: true
The internal device MUST respond with:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://public-website.com
Access-Control-Allow-Private-Network: true
If Access-Control-Allow-Private-Network: true is missing, the browser blocks the connection to the internal IP address.
C. Interactions with Cookie SameSite Attributesβ
CORS enforcement operates independently of HTTP cookie SameSite rules:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β COOKIE SAMESITE vs CORS INTERACTION β
βββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Cookie Attributeβ Browser Behavior on Cross-Origin Fetch/XHR β
βββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SameSite=Strict β Cookie is NEVER sent on cross-origin requests, regardlessβ
β β of CORS headers returned by the server. β
βββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SameSite=Lax β Cookie is sent ONLY for top-level navigation (GET). β
β β Blocked for cross-origin Fetch/XHR POST requests. β
βββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SameSite=None β Cookie IS sent for cross-origin requests if β
β (Requires Secureβ withCredentials=true, subject to CORS ACAO/ACAC rules. β
βββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ