API Works in curl but Fails CORS in the Browser? Check CDN Preflight, Credentials, and Cache Variants
Create Time:2026-10-28 11:14:27
浏览量
1001

Browser requests pass through a CDN and a security gate, contrasting permitted preflight traffic with a blocked request.

curl returns JSON from an API, but the same endpoint produces a CORS error in your front end. That is entirely possible: curl can display a response without applying a browser's cross-origin reading restrictions. After adding a CDN, an OPTIONS request might be blocked, response headers might be overwritten, or responses carrying permission for one origin might be reused for another.

Start in the browser's Network panel. Determine whether the failing response belongs to the OPTIONS preflight or the actual application request. They require different checks. The commands below use Bash and curl with example domains; they have not been tested against your production environment.

Record the page origin and the request the browser actually sends

An origin consists of a scheme, hostname, and port. https://app.example.com and https://api.example.com are different origins even though they share a parent domain. Cookie same-site rules and same-origin checks are separate concepts.[1]

Record the page's Origin, request URL, method, headers, and credentials mode. Preserve the preflight and actual response, noting redirects, status codes, and request IDs. Redact cookies, Authorization values, signed parameters, and user data before sharing logs.

Not every cross-origin request needs a preflight. Requests satisfying the CORS safelist conditions can be sent directly; JSON Content-Type, custom headers, or certain methods commonly trigger preflight. A browser's CORS error can also obscure an underlying CDN 403, gateway 502, or redirect to a login page. Correlate the response with server logs before changing permissions.

Reproduce the preflight without treating curl as a browser

The following request models a page preparing to send a JSON POST with Authorization. It sends only OPTIONS and does not create an order:

curl -sS --max-time 20 -D - -o /dev/null -X OPTIONS \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: authorization,content-type' \
  https://api.example.com/v1/orders

On Windows, use curl.exe where necessary and adapt the line continuation and null output device. Match the method and requested header list to the browser's actual preflight. curl does not enforce CORS; it helps reveal what the edge returns.

A successful preflight commonly returns 200 or 204 with the appropriate Access-Control-Allow-Origin, allowed methods, and allowed request headers. A 204 alone proves little: the browser can still refuse to proceed when permission headers are missing, the origin does not match, or Authorization is not explicitly permitted.[1][2]

If a WAF, authentication middleware, or router rejects OPTIONS, identify the layer generating the response. Standard cross-origin preflights do not carry the application request's credentials, such as its cookies, and should not require an ordinary authenticated session first. Permitting a preflight does not authorize the order operation: the actual request must still pass authentication and authorization. Do not remove protection from an entire API just to eliminate an OPTIONS 403.

Check the actual response after preflight succeeds

Passing preflight only permits the browser to proceed with the corresponding request. The actual response still needs valid CORS headers before the script can read it.

For a safe, publicly readable status endpoint, inspect the actual response with:

curl -sS --max-time 20 -D - -o /dev/null \
  -H 'Origin: https://app.example.com' \
  https://api.example.com/v1/status

This observes that endpoint's response; it does not validate the complete order POST workflow. Do not replay payments, deletions, or duplicate submissions in production. Use test accounts and an isolated environment for state-changing operations.

Check successful responses as well as 401, 403, and application-error paths. Letting an approved front end read its own error response usually improves diagnostics, but does not justify exposing private error content to arbitrary origins. If the CDN, reverse proxy, and application all add Access-Control-Allow-Origin, duplicate fields can also cause rejection.

If the page needs to read a custom response header, such as a tracing ID, evaluate Access-Control-Expose-Headers. Headers a script may send and headers it may read from a response are separate permission lists.

Match credentialed requests to an explicit origin

For cross-origin cookies sent using a mode such as credentials: "include", the server needs an explicitly matching Access-Control-Allow-Origin and Access-Control-Allow-Credentials: true. A wildcard origin cannot be used for this credentialed access. Explicitly list the allowed methods and headers as appropriate; Authorization needs explicit permission rather than reliance on a wildcard.[1]

Access-Control-Allow-Origin cannot contain a comma-separated list of origins. To support multiple sites, maintain an exact allowlist and return the matching approved origin. Do not reflect arbitrary Origin values without validation or automatically trust a null origin.

CORS approval does not ensure that a cookie will be sent. SameSite, Secure, cookie domain and path scope, and browser third-party-cookie policies still apply. Diagnose Authorization-related preflight requirements separately from cookie delivery.

CORS mainly controls browser scripts' ability to read responses. It does not replace authentication, authorization, or CSRF defenses. Some requests can reach the application and cause side effects even when their responses are unreadable. Do not automatically retry a write operation indefinitely after a CORS error.

Check cache variants when permissions depend on Origin

When an application returns different Access-Control-Allow-Origin values for different allowlisted origins, the response should generally include Vary: Origin. Then verify the CDN's actual caching configuration. Some services require explicit cache-key configuration or bypass rules rather than automatically producing the expected variants from Vary.[1]

A typical failure starts when site A populates an object containing permission for A. Site B later hits that same object and receives A's permission header, so B's browser rejects access. Updating application code alone leaves already cached incorrect responses to be addressed.

If your provider supports edge caching for OPTIONS, verify its handling of Origin, Access-Control-Request-Method, and Access-Control-Request-Headers. Ordinary HTTP caching does not cache OPTIONS by default. Do not force all methods to share cached objects simply to improve a hit ratio.

For authenticated or user-specific APIs, separately establish whether shared caching is allowed at all. Adding Origin to a cache key does not prevent different users at that origin from sharing private data. Cookie handling, Authorization, Cache-Control, and cache bypass policies still need to be correct.

Separate browser preflight caching from CDN object caching

Access-Control-Max-Age governs how long a browser may retain preflight permissions, subject to browser-specific caps. The preflight cache is separate from the ordinary HTTP cache. This header is not a universal CDN TTL.[2][3]

After an edge purge, an existing browser can still retain an earlier preflight result. Changing Max-Age cannot recall permissions already stored there. Compare a clean browser environment and observe whether OPTIONS is actually sent; a normal refresh does not prove every relevant state has been cleared.

Enforce tightened permissions on actual application requests immediately. Do not rely on old preflight approvals expiring to protect an endpoint. During debugging, a shorter preflight cache lifetime can be appropriate; adjust it later according to performance and security requirements.

Validate a scoped change and keep a rollback record

Back up the origin allowlist, CDN cache rules, WAF configuration, and response-header policies before editing. Test a dedicated hostname or low-risk path instead of enabling cross-origin access across the entire site.

Acceptance checks should cover approved and unapproved origins, requests with and without credentials, both preflight and actual responses, success and error paths, and alternating requests from two approved origins to reveal cache mixing. Browser observations, edge responses, and application logs should correlate to the same requests.

For a 301 or 302, check whether the API redirects to a login page, another domain, or a different scheme. Preflight redirect handling has compatibility differences; have clients request the intended endpoint directly rather than depending on a complex redirect chain. Mixed content, certificate errors, and network reachability require their own fixes too.

For rollback, restore the last verified policy, address affected cached objects, and retest existing browser sessions. Do not hide the problem with mode: "no-cors": it generally produces an opaque response that the script cannot read, not usable JSON access.

Keep the failing stage, response-generating layer, Origin-to-permission mapping, and cache observations in the incident record. Those details determine whether the next change belongs in routing, authentication, response headers, or cache policy.

References

Sources checked for the paired article on October 28, 2026. Consult your provider's current documentation for OPTIONS forwarding and edge-caching behavior.

  1. MDN: Cross-Origin Resource Sharing (CORS)
  2. MDN: Preflight request
  3. MDN: Access-Control-Max-Age