First establish the actual browser requirements
An origin consists of scheme, host and port. https://app.example.com and https://api.example.com are different origins, as are two different ports on the same host. The path is not part of the origin. Browser access within the same origin needs no additional CORS permission. Pure server integrations do not need it either.
For each browser client, record its actual origin, required API routes, methods and request headers. Note whether responses are public or user-specific. This short list is the configuration's starting point. A forgotten test domain should not lead to reflecting arbitrary origins unchecked.
The basic protocol is described in the Fetch Standard, section 3.3. The web guide to cross-origin headers covers the separate browser measures COOP, COEP and CORP; they do not replace this API permission.
Distinguish public and credentialed responses
For a deliberately publicly readable data endpoint, the following response permission may be appropriate:
Access-Control-Allow-Origin: *
This is a synthetic header excerpt, not a complete server configuration. The wildcard alone does not prove a security defect. Instead, establish whether the response is truly intended for arbitrary browser origins and contains no unintentionally protected content.
If a known frontend should read responses using Fetch mode credentials: "include", explicit permission is required, for example:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
The value true must be written exactly this way. Combining include with Access-Control-Allow-Origin: * does not work, even if a particular request currently contains no cookie. The credentials mode is decisive. Conversely, include does not guarantee that a cookie is sent: cookie attributes and browser policies also apply. Fetch, section 3.3.5, shows the combinations.
Do not confuse this mode with an Authorization header explicitly set in application code. Its CORS permission comes through allowed request headers. A manually set bearer token does not automatically mean the call needs credentials: "include".
Allow origins through a fixed trust decision
Access-Control-Allow-Origin contains a matching origin or the wildcard, not a comma-separated list of websites. If multiple frontends are permitted, the server compares the incoming origin with a maintained allowlist and returns exactly that origin for an allowed case.
Check complete origins. A substring search for example.com may include unwanted names. Blanket reflection of $http_origin or origin: true contains no trust decision by itself. Nor does null mean ‘no risk’: different document contexts may have an origin serialised this way. For an ordinary protected browser API, do not add it to the list as a precaution.
A trusted origin is also an ongoing operational responsibility. If you no longer control a former preview or partner domain, review and remove its permission. The application remains responsible for actual data permissions.
Configure preflight and actual responses separately
A JSON request with Content-Type: application/json, a custom request header or certain methods may trigger preflight. The browser sends OPTIONS with the origin, intended method and, where applicable, header names. It does not already include the bearer token or session cookie of the later request.
For a restricted JSON client, a synthetic preflight response might look like this:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, X-CSRF-Token
Vary: Origin
This example assumes cookie-based requests with a CSRF header. If your API instead needs Authorization, explicitly allow that header. A wildcard in Access-Control-Allow-Headers does not cover Authorization. Credentialed responses also need appropriate explicit configuration in the other permission lists. See Fetch: request headers and preflight checking.
A successful preflight authenticates no user. The actual API request must still pass every access, permission and input check afterwards. The actual response also needs the appropriate origin and, where applicable, credentials permission. This includes error responses the allowed client needs to inspect. A CORS list of allowed methods does not implement server-side method restrictions either.
Schematic browser flow · no measurement
Preflight, API request and permission to read
The frontend at https://app.example.com calls the API at https://api.example.com in CORS mode. In the preflight path, there is no usable cached preflight permission.
Simple request without preflight
- Browser → API: request
This simple request does not require a preflight. The browser sends it directly.
- API → browser: response
The API processes the request with its own access, permission and input checks.
- Browser → frontend: allow or block reading
Only a matching CORS permission makes the response readable by frontend code.
Reading blocked: the request has still already been sent.
Request requiring preflight
- Browser → API:
OPTIONSThe preflight names the origin, method and, where applicable, header names. It does not yet contain the bearer token or session cookie of the later request.
- API → browser: preflight response
The browser checks permissions for the origin, method and headers. A requested
Authorizationheader must be explicitly allowed.Preflight rejected: the browser does not send the actual request.
- Browser → API: actual request
After a successful preflight, the browser sends the request. The API still checks access, permissions and input.
- API → browser → frontend: check the response
The actual response also needs the matching CORS permission. Otherwise, the browser blocks reading.
With credentials: "include": the CORS permission needs the specific origin in Access-Control-Allow-Origin and Access-Control-Allow-Credentials: true. An origin wildcard is insufficient even if no cookie is actually present. This applies to the required preflight permission and to the actual response. Cookie attributes and browser policies also determine whether cookies are sent.
CORS does not authenticate a user or replace API authorisation. Other HTTP clients are not bound by this browser rule. A manually set Authorization header does not automatically require credentials: "include".
Account for caches and readable response headers
If origin permission depends on the request, the cache must account for that distinction. Vary: Origin identifies the corresponding response variant. Preserve existing Vary values instead of overwriting them. A fixed wildcard response does not need this distinction solely because of CORS. Fetch: CORS and HTTP caches explains the basis.
This does not yet prevent incorrect caching of user-specific data. Its storage rules are an additional decision; the Cache-Control guide provides the foundations. A CORS header does not separate user sessions in a shared cache.
If JavaScript needs to read a custom response header such as X-Request-ID, it may require Access-Control-Expose-Headers: X-Request-ID. This differs from Access-Control-Allow-Headers, which concerns outgoing request headers. Exposing the name does not automatically make its content suitable; a correlation identifier should contain no confidential data either.
Test a change with permitted and forbidden cases
In your own environment, test the normal browser client, a second origin you control that is not allowed, a matching preflight and the actual response. Include success, authentication and business error paths. For cookies, test both signed-in and signed-out states and the browser conditions actually supported.
An HTTP tool can document returned headers. Whether browser code can read a response and which cookies the browser sends must be tested in the browser flow. Also test server-side authorisation with a client without Origin; it must not depend on CORS permission.
Introduce changes for clearly known clients first. Keep the previous allowlist and configuration ready for rollback. If permission was accidentally too broad, close the affected origins and investigate affected responses; a header finding alone does not prove data leakage. Test again after rollback, accounting for cached preflights too.
What a public CORS finding means
The API Security Check observes selected public GET responses with test origins and supplementary OPTIONS information. It sends no user credentials and executes no signed-in browser flow. Broad permission is therefore a concrete configuration signal; whether protected data is readable additionally depends on response content, authentication and browser conditions.
For implementation, continue with the nginx or Express guides. If configuration is already in place, CORS error diagnosis helps. Sources checked: 27 September 2026.
Sources and date
Sources checked: 27 September 2026. Links also appear alongside the statements they support.