CORS configuration tells a browser when JavaScript from one origin may read a response from another origin. It is commonly encountered when a frontend and API use different domains, subdomains, or ports. A permissive fix can silence an error while exposing information to websites that were never meant to access it.
A secure implementation begins with the application’s intended sharing model. This guide explains what CORS controls, where it does not provide protection, and how to verify the policy in a browser without disabling browser security.
1. Define the origins that need access
An origin consists of a scheme, hostname, and port. A development frontend on one port is a different origin from the same hostname on another port. Similarly, an HTTPS application and an HTTP page are not interchangeable just because their hostnames match.
List the production origins that genuinely need API access. Keep development and preview environments explicit rather than authorizing every possible subdomain. Review who can publish content on each allowed origin, because you are extending a browser-readable trust relationship to code served there.
Use structured URL parsing and exact comparisons. A hostname that merely contains your domain as text is not necessarily yours. Reject unknown origins by default and document how new approved clients enter the allowlist.
2. Separate public sharing from credentialed data
A public resource that contains no user-specific information may intentionally allow any origin to read it with Access-Control-Allow-Origin: *. That is a product decision, not a default setting for every endpoint. Keep sensitive and public response paths distinct.
For credentialed browser requests, the server must return a specific permitted origin rather than the wildcard. The browser also needs the appropriate Access-Control-Allow-Credentials response value when exposing a credentialed response. Client-side credentials settings and server-side policy must work together.
Do not reflect whatever Origin header arrives without validation. Reflection may appear to make the integration work for everyone, but combined with credentialed access it can permit an untrusted website to read authenticated responses. Consult MDN’s CORS guide for the precise header behavior.
3. Handle preflight requests deliberately
Some browser requests require a preflight OPTIONS request before the actual request. The browser asks whether the origin, method, and requested headers are permitted. The server’s response should grant only the methods and headers supported for that resource.
Preflight requests do not include credentials under the CORS protocol. A gateway that demands the same authenticated session for every OPTIONS request can therefore break legitimate cross-origin access. Allow the necessary policy negotiation without making protected application data public.
Do not route preflight requests through business logic that changes state. They are a negotiation step, not an instruction to create or update data. Verify that reverse proxies, application middleware, and edge services agree on the policy rather than each adding conflicting headers.
4. Account for caches and error responses
When Access-Control-Allow-Origin varies according to a validated request origin, include Origin in the Vary response header. This tells relevant caches that different origins can receive different response variants. Avoid overwriting existing Vary values that are needed for other dimensions.
Check caching independently for sensitive data. Correct CORS headers are not a substitute for safe cache-control settings on personalized responses. A shared cache should not serve one user’s private body to another user simply because both origins are permitted.
Inspect expected error responses as well as successful requests. A browser may hide a useful authentication or validation response when the applicable CORS headers are missing. Add the intended policy consistently, while ensuring that error bodies do not disclose stack traces or secrets.
5. Do not confuse CORS with authentication or CSRF protection
Non-browser clients do not have to enforce the browser’s same-origin restrictions. A command-line tool can send an HTTP request whether or not the server supplies CORS headers. Authentication and authorization must remain server-side controls for every relevant client.
CORS also does not universally stop a request from reaching the server. Certain cross-origin requests can be sent without preflight, and the browser may block only the response from being read. A console error is therefore not proof that a state-changing action was prevented.
For cookie-authenticated actions, use appropriate CSRF defenses. Our SameSite cookie guide explains a related credential-delivery control, but cookies and CORS should be reviewed as distinct pieces of the design.
6. Test the real browser workflow
Use your actual frontend origin and a supported browser. Capture the request’s Origin, any preflight request, the response headers, and the client credentials mode. Check the browser’s network panel instead of relying solely on an application-level error string.
Test an approved origin, an unapproved origin, an absent Origin header, and any special null-origin behavior your application expects. Do not automatically allow Origin: null; different contexts can produce it, and it does not identify a trusted website.
Test methods and headers beyond the simplest successful call. Authorization headers, custom application headers, and JSON requests can alter preflight behavior. Confirm both positive and negative outcomes, including whether a rejected actual request changed server-side state.
If a command-line request succeeds while the browser fails, compare the browser-specific sharing rules first. The command-line result can confirm reachability or server behavior, but it cannot demonstrate that browser CORS checks passed.
7. Keep the policy maintainable
Store origins in a controlled configuration with an owner and change review. Remove retired preview deployments and temporary development exceptions. A long-lived allowlist should describe active clients, not the history of every debugging session.
Avoid teaching users to launch a browser with security disabled, install bypass extensions, or send secrets through an untrusted public proxy. Those workarounds change the trust model rather than repairing the server’s intended policy.
Add regression tests for policy generation and integration tests from representative browser origins. Review the configuration when domains, gateways, authentication mechanisms, or deployment platforms change. CORS mistakes often emerge at boundaries between components that each appear correct in isolation.
A deployment review checklist
- Origins match exact approved scheme, host, and port combinations.
- Credentialed responses use an explicit allowed origin.
- OPTIONS negotiates only supported methods and headers.
- Dynamic origin responses preserve the appropriate Vary value.
- Personalized response caching is reviewed separately.
- Authentication, authorization, and CSRF controls remain active.
- Browser tests include untrusted origins and failure responses.
Frequently asked questions
Should I use a wildcard to fix a CORS error?
Only if the resource is intentionally readable by any website and its data-sharing model supports that choice. A wildcard is not suitable for credentialed access to personalized information.
Does allowing an origin authenticate its users?
No. An allowed origin identifies where browser code is served, not which person or account is authorized. Keep ordinary authentication and record-level authorization checks on the API.
What is the safest debugging approach?
Inspect browser requests and response headers, then repair the narrow policy mismatch. Keep security enabled, test the actual credentials mode, and change only the origins, methods, or headers the application demonstrably requires.