Cross-Origin Resource Sharing (CORS) is the browser mechanism that controls when JavaScript on one origin can access resources on another. It's confusing because the protections are mostly browser-imposed (servers usually allow everything; browsers block).
This page is about how CORS actually works and the configuration patterns.
By default, browsers prevent JavaScript on https://app.example.com from making requests to https://api.example.com. The "origin" is scheme + host + port.
The protection is for users, not servers. The point: a malicious page can't read your bank's data even if you're logged in.
Without CORS, every cross-origin XHR/fetch would be blocked. Modern web apps make many cross-origin requests legitimately (calling APIs, embedding fonts, loading images). CORS is the mechanism for the server to opt into allowing specific cross-origin requests.
GET, HEAD, or POST with simple content types (form-data, text/plain) and no special headers can be sent immediately. The browser includes:
Origin: https://app.example.com
The server responds with:
Access-Control-Allow-Origin: https://app.example.com
(Or * for any origin, with limitations.)
If the response has the right header, the browser lets JS read it. If not, the browser blocks JS access (the request was sent and the response received, but JS can't read it).
For requests that aren't simple (DELETE, PUT, custom headers, JSON content type), the browser sends an OPTIONS preflight request first:
OPTIONS /api/orders
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization
Server responds:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 3600
The browser then sends the actual request.
The preflight is cached per the Max-Age; subsequent requests skip it.
Access-Control-Allow-OriginThe origin that's allowed. Either a specific origin or * (any).
* doesn't work with credentials (cookies, auth headers). For authenticated cross-origin requests, you must specify the origin.
Access-Control-Allow-CredentialsIf true, cookies and auth headers can be sent. Requires specific Allow-Origin (not *).
Access-Control-Allow-MethodsMethods the resource supports. For preflight responses.
Access-Control-Allow-HeadersHeaders the request can include. For preflight responses.
Access-Control-Expose-HeadersHeaders JS can read. By default, only a subset of response headers are visible to JS; this header expands the set.
Access-Control-Max-AgeHow long preflight result is cached. Longer means fewer preflights.
Allow-Origin: comes from a list — production-app.example.com, staging.example.com, localhost:3000 for dev
Each request's Origin header is checked against the list; matching origin is echoed back. Not in list: no header, browser blocks.
This is the right pattern for production APIs.
Access-Control-Allow-Origin: *
For genuinely public APIs (read-only, no credentials needed), wildcard is fine.
If you control multiple subdomains:
*.example.com → all subdomains allowed
Implemented by checking the Origin header; not by literal wildcard in the header value (browsers don't support wildcards in Allow-Origin beyond *).
Browsers reject. Specify one origin (echoed from request) or *.
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Browser rejects. Use specific origin.
The preflight needs a 200/204 response. If the API returns 401 for OPTIONS (because not authenticated), CORS fails.
Configure the framework to short-circuit OPTIONS to a CORS-aware handler before auth.
Different origins might have different CORS rules. If responses are cached, the browser might return the wrong CORS headers. Use Vary: Origin to inform caches.
A common misunderstanding: CORS protects the server. It doesn't.
CORS protects users from malicious scripts in their browser reading data they shouldn't. The server still receives every request; the browser is what blocks JS access to responses.
Server-side authentication and authorization are still required. CORS is in addition to, not instead of.
Standard CORS. Origin specific or wildcard.
Specific origin; allow-credentials true; cookies or auth headers.
CORS doesn't apply. Servers don't enforce same-origin policy. CORS is browser-only.
Mobile apps don't have a browser; CORS doesn't apply. The app's HTTP client just makes the request.
Same — no CORS.
Access-Control-Allow-Origin: * with credentials. Doesn't work; pick specific origin.