CORS Configuration Best Practices for Browser Facing APIs
Quick answer
CORS, or Cross-Origin Resource Sharing, lets a server tell browsers which other origins may read its responses. Best practice is to allow only specific,...
CORS, or Cross-Origin Resource Sharing, lets a server tell browsers which other origins may read its responses. Best practice is to allow only specific, trusted origins from an explicit list, avoid the wildcard when credentials are involved, cache preflight responses, and send the Vary header when the allowed origin depends on the request. CORS is a browser rule, not an authentication system, so it never replaces proper authorization on the server.
What Does CORS Actually Protect?
Browsers enforce the same-origin policy, which stops a script on one origin from reading responses from another. CORS is a controlled relaxation of that policy: the server declares through response headers which origins may read its data.
Understanding the scope prevents misconfiguration. CORS restricts what browser-based scripts can read. It does not stop requests from being sent, and it does not stop non-browser clients such as scripts, servers, and command line tools. A request from curl ignores CORS entirely, so any protection you need, such as authentication and rate limiting, must be enforced on the server.
The reason for caution is that a too-permissive policy lets a malicious website, visited by a logged-in user, read that user's data from your API through their browser.
How Should You Configure Allowed Origins?
Prefer an explicit allow list of origins. The server compares the incoming Origin header with the list and, if it matches, echoes that exact origin in the Access-Control-Allow-Origin header. A wildcard is acceptable only for truly public, unauthenticated resources.
- Match origins exactly, including scheme, host, and port, and avoid loose substring or suffix checks that attackers can satisfy with lookalike domains.
- Do not reflect any incoming Origin value blindly. That is equivalent to allowing everyone.
- Do not allow the null origin, which can be produced by sandboxed documents and some redirects.
- Keep the list in configuration, and review it when sites are retired or domains change.
When the allowed origin varies by request, add Origin to the Vary header. Otherwise, a cache or CDN may serve a response with one origin's header to a different origin, causing failures or exposure.
How Do Credentials and Preflight Requests Work?
By default, cross-origin requests do not include cookies or HTTP authentication. If your frontend sends credentials, the server must respond with Access-Control-Allow-Credentials set to true, and the allowed origin must be a specific origin, not a wildcard. Browsers reject the wildcard combination.
For requests that are not simple, such as those with custom headers, JSON content types, or methods like PUT and DELETE, browsers first send a preflight request using the OPTIONS method. The server answers with the allowed methods and headers, and the browser then decides whether to send the actual request.
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600
Vary: Origin
Access-Control-Max-Age lets browsers cache the preflight result, reducing extra round trips. Browsers cap the maximum value, so confirm the limits for the ones you support.
What Mistakes Cause the Most Trouble?
Many CORS problems come from misunderstanding where headers are set. If your application and a reverse proxy both add CORS headers, the browser may see duplicates and reject the response. Choose one layer to own CORS.
- Setting the headers only on successful responses. Error responses, such as 401 and 500, also need them, or the browser hides the real error from your frontend.
- Forgetting to handle OPTIONS requests in authentication middleware, so preflights are rejected before CORS headers are added.
- Allowing broad methods and headers unnecessarily. List only what the frontend uses.
- Using CORS to solve problems that are really routing issues. Serving the frontend and API from the same origin, or through a reverse proxy, avoids CORS completely.
- Exposing response headers that scripts need without listing them in Access-Control-Expose-Headers.
Test with a real browser and the developer tools network tab, which shows preflights and explains blocked requests.
Frequently Asked Questions
Is CORS a security feature for my API?
It protects users in browsers from cross-site data reads, but it does not authenticate clients. Always enforce authorization on the server.
Can I use a wildcard with credentials?
No. When credentials are included, the allowed origin must be a specific origin.
Why do I see a CORS error when the server returns 500?
Error responses without CORS headers are blocked by the browser, hiding the actual status from scripts.
Do server to server calls need CORS?
No. CORS applies only to browsers enforcing the same-origin policy.
Summary
Use explicit origin allow lists, handle credentials and preflights correctly, set Vary and cache preflights, and keep CORS in one layer. Remember it is not authentication. Review current browser documentation for details.