CORS Explained: Cross-Origin Resource Sharing and Security
Cross-Origin Resource Sharing, or CORS, is a browser mechanism that allows a server to relax the same-origin policy in a controlled way, permitting selected cross-origin requests to read its responses. It is one of the most misunderstood web platform features, and misconfigurations can expose sensitive data to malicious sites. Understanding how CORS works is essential for building APIs that are both usable across origins and secure.
The Same-Origin Policy and Why CORS Exists
An origin is the combination of scheme, host, and port. The same-origin policy is a fundamental browser rule that prevents a document from one origin from reading responses from another origin. This stops a malicious site from reading your webmail or bank data using your logged-in session.
However, legitimate applications often need to call APIs on other origins, such as a front-end on one domain talking to an API on another. CORS provides a standardized way for a server to tell the browser which other origins are allowed to read its responses, loosening the policy only where the server explicitly opts in.
How CORS Works
CORS is driven by HTTP headers exchanged between the browser and server. The browser adds an Origin header to cross-origin requests, and the server responds with Access-Control-* headers indicating what it permits. The browser, not the server, enforces the result: if the response lacks the right headers, the browser blocks the calling script from reading it.
Simple Requests
Certain low-risk requests, such as a GET or a form-style POST with basic headers, are sent directly. The browser includes the Origin header and checks the response's Access-Control-Allow-Origin before exposing the body to script.
Preflighted Requests
For requests that can change state or use non-simple methods and headers, the browser first sends a preflight using the OPTIONS method to ask permission:
OPTIONS /api/data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, PUT, POST
Access-Control-Allow-Headers: Content-Type
Only if the preflight response approves the method and headers does the browser send the real request.
Credentialed Requests
By default, cross-origin requests do not include cookies. When an application opts in to sending credentials, the server must respond with Access-Control-Allow-Credentials: true and must specify an exact origin in Access-Control-Allow-Origin. The wildcard * is not allowed together with credentials.
Caching Preflight Responses
Preflight checks add a round trip, so a server can set Access-Control-Max-Age to let the browser cache a preflight result for a period. This improves performance, but it means a policy change may not take effect until cached entries expire, so the value should be chosen deliberately rather than set to a very long duration.
Common CORS Misconfigurations
Because CORS relaxes a security boundary, mistakes can be serious.
- Reflecting the Origin with credentials. Echoing back any
Originvalue while also allowing credentials effectively lets every site read authenticated responses. Validate the origin against an allowlist instead. - Allowing the null origin. Permitting
Origin: nullcan be abused, because sandboxed documents and some contexts sendnull. - Overly broad wildcards. Using
*for internal or authenticated APIs exposes data more widely than intended. - Forgetting Vary Origin. When the allowed origin is chosen dynamically, omitting
Vary: Origincan cause caches to serve one origin's permissive response to another.
Building a Secure CORS Configuration
- Default to closed. Do not enable CORS unless a cross-origin client genuinely needs to read responses.
- Use a strict allowlist. Compare the incoming
Originagainst a known-good list and return that exact value, never a blind reflection. - Be careful with credentials. Only allow credentials for trusted origins, and never combine them with a wildcard.
- Scope methods and headers. Permit only the methods and headers the client requires.
- Set Vary Origin. Ensure caches distinguish responses per origin.
What CORS Does Not Protect Against
A frequent misconception is that CORS prevents requests from reaching the server. It does not. CORS controls whether the browser lets script read the response; the request may still arrive and cause server-side effects. This is why CORS is not a defense against CSRF, which is about unwanted requests being sent, and why it is unrelated to SSRF, a server-side issue. Server-side authorization and anti-CSRF controls remain necessary regardless of CORS settings. In short, CORS controls who may read a response in the browser, not who may trigger the underlying action, which must always be enforced on the server.
Key Takeaways
- CORS lets a server selectively relax the same-origin policy so approved origins can read its responses.
- The browser enforces CORS using the
OriginandAccess-Control-*headers, with preflight requests for non-simple calls. - Credentialed requests require an exact origin and
Access-Control-Allow-Credentials: true, never a wildcard. - Dangerous misconfigurations include reflecting arbitrary origins with credentials and allowing the null origin.
- CORS governs reading responses, not sending requests, so it does not replace CSRF protection or server-side authorization.