Cybersecurity/Networking

CORS Configuration Best Practices for APIs and Browser Clients

4 min read by DebuggedIt

Quick answer

Quick answer: CORS is a browser mechanism that lets a server declare which other origins may read its responses. Configure an explicit allowlist of trusted...

Quick answer: CORS is a browser mechanism that lets a server declare which other origins may read its responses. Configure an explicit allowlist of trusted origins, never reflect the request origin blindly, avoid the wildcard when credentials are involved, and add a Vary: Origin header. CORS is not authentication, so it never replaces proper access control on the server.

What Does CORS Actually Protect?

Browsers enforce the same-origin policy, which stops a page from one origin from reading responses from another. CORS is a controlled relaxation of that rule. The server sends headers that tell the browser which origins may read the response. It is enforced by the browser, not the server, so tools like curl and server-to-server calls ignore it completely.

That fact shapes how you should think about it. CORS protects users from malicious pages reading their data through their own browser, using their cookies. It does not stop an attacker from sending requests directly to your API from a script. Your API must therefore authenticate and authorize every request on its own, and treat CORS as a browser-side safety layer only.

Requests are classified as simple or preflighted. Simple requests, using basic methods and headers, are sent directly and the browser checks the response headers afterward. Requests that use other methods, custom headers, or certain content types trigger a preflight: the browser first sends an OPTIONS request asking permission, and only proceeds if the server's answer allows the method and headers.

How Should You Configure the Headers?

The key header is Access-Control-Allow-Origin, which names the allowed origin. Safe configuration follows a few principles:

  • Maintain an allowlist of exact origins, including scheme and port, and compare the request's Origin header against it.
  • If the origin is on the list, echo that specific origin back. If it is not, omit the CORS headers.
  • Do not reflect any origin you receive. Blind reflection effectively allows every site and is a frequent security mistake.
  • Add Vary: Origin so caches do not serve a response for one origin to another.
  • List only the methods and headers your API needs in Access-Control-Allow-Methods and Access-Control-Allow-Headers.

Credentials need extra care. If browsers send cookies or HTTP authentication, the server must set Access-Control-Allow-Credentials to true, and in that case the wildcard origin is not permitted: you must name the specific origin. Be especially cautious with allowing credentials for broad or user-controlled origins, because it exposes authenticated responses to those sites.

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 600
Vary: Origin

What Mistakes Are Most Common?

Using a wildcard for convenience is the classic one. It is acceptable for truly public, unauthenticated resources, but not for APIs that return user-specific data. Another is handling CORS in several layers at once, such as the proxy and the application, which can produce duplicate headers that browsers reject. Pick one layer and keep the configuration there.

Forgetting to answer preflight requests correctly causes many mysterious failures. The OPTIONS request must return a success status with the right headers, and it must not require authentication, because browsers do not send credentials on preflight. A max-age value lets browsers cache preflight results for a while, reducing extra requests, though browsers cap the maximum.

Error responses also need CORS headers. If a 401 or 500 lacks them, the browser reports a CORS failure and hides the real status from your frontend code, which makes debugging confusing. Test with real browser requests and inspect both the preflight and the actual response, since curl will not reproduce browser behavior.

Frequently Asked Questions

Does CORS protect my API from attackers?

No. It only restricts what browsers allow pages to read. Attackers can call your API directly, so enforce authentication and authorization on the server.

Why can I not use a wildcard with credentials?

Browsers forbid it for safety. When credentials are included, the server must name the specific allowed origin instead of using a wildcard.

Why does my request work in curl but fail in the browser?

Curl does not enforce CORS. The browser blocks the response if the CORS headers are missing or wrong. Check both the preflight and the actual response headers.

Do I need Vary: Origin?

Yes, whenever the response differs by origin. It prevents shared caches from serving one origin's headers to another origin.

Summary

Treat CORS as a browser-side relaxation, configure an exact origin allowlist, handle preflight and credentials correctly, and set CORS in one layer. Keep real access control on the server. Check current browser and framework documentation for details on headers and limits.