TATECHATLAS
◎ Français
Web et API

curl works, fetch fails: diagnosing CORS and preflight

Distinguish response access from request sending, inspect an OPTIONS preflight and configure explicit origins for credentialed browser requests.

Dans ce guide

curl does not enforce browser CORS rules. A successful curl response therefore does not establish that JavaScript on another origin can read it. Inspect the browser Network panel: some requests are sent directly, while requests such as a JSON POST or one with Authorization usually need an OPTIONS preflight. Check the preflight and actual response separately, using the page’s exact origin and the intended method and headers.

An origin is a scheme, host and port

A page at https://app.example and an API at https://api.example have different origins even though their names share a suffix. Changing the port or switching from HTTP to HTTPS can also change the origin. Start with the browser’s actual Origin header rather than guessing from a deployment name.

CORS governs whether a browser exposes a cross-origin response to a script. It is not API authentication and does not stop curl or another server from calling the API. Keep authorization and protection against unwanted state-changing requests in the application.

Direct requests and preflight are different paths

A GET without non-safelisted request headers can often be sent without preflight. Certain POST requests using form-compatible content types can also take this path. The response still needs appropriate CORS headers before JavaScript can read it.

A request using application/json, Authorization or a method such as PUT generally requires preflight. The browser asks which method and request headers are allowed before sending the application request. Credentials alone do not mean every request must be preflighted; inspect the complete request conditions.

A concrete JSON POST example

Assume a page on https://app.example sends a JSON POST to https://api.example/items with a bearer token. These are illustrative hosts and a hypothetical endpoint, not a working service. The code below belongs in the browser page; the token is a placeholder.

Without a valid cached preflight result, expect an OPTIONS request advertising POST and the authorization and content-type headers. The browser constructs these preflight headers; application JavaScript should not set them manually.

fetch('https://api.example/items', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer DEMO_TOKEN'
  },
  body: JSON.stringify({name: 'sample'})
}).then(response => {
  if (!response.ok) throw new Error('HTTP ' + response.status);
  return response.json();
}).then(console.log).catch(console.error);

Read the preflight before changing server settings

In Developer Tools, preserve the network log and locate OPTIONS. Its Origin should identify the page, and Access-Control-Request-Method should be POST in this example. Access-Control-Request-Headers lists requested non-safelisted headers. Header-name case is not significant.

A suitable successful preflight response allows the specific origin, method and headers. OPTIONS should not require the bearer token intended for the subsequent POST, because the preflight does not carry that application header. Authentication remains necessary on the actual request.

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: authorization, content-type
Vary: Origin

The actual response needs its own permission

Passing preflight is only the first step. The POST response also needs Access-Control-Allow-Origin. Inspect errors as well as success responses: an authentication failure without the appropriate CORS header can look like a generic network failure to JavaScript.

If OPTIONS succeeds but the POST fails, inspect its status, application logs and response headers. A CORS message does not prove that authentication or application logic succeeded. Conversely, a directly sent request may have reached the server even when the browser blocks script access to its response.

Cookies require explicit origin handling

To send cross-origin cookies with fetch, use credentials: include. The server must allow credentials and respond with the explicit permitted origin, not Access-Control-Allow-Origin: *. Browser cookie rules, including SameSite and third-party restrictions, still apply.

Maintain an allowlist when reflecting an incoming origin; blindly echoing every origin grants access too broadly. When a response varies by Origin, Vary: Origin helps caches separate those variants. Match the origin exactly; paths are not part of an origin.

Compare the browser request with the command line

A command-line call can help inspect the HTTP status and returned headers, but it does not reproduce the browser’s enforcement. Compare method, Origin, request headers, redirects and credential handling. A successful plain GET does not diagnose a failing authenticated JSON POST.

Do not use mode: no-cors to obtain readable API data. It can produce an opaque response whose body and status are unavailable to the script. Fix the server’s permission for the intended request instead of treating an opaque result as a successful integration.

A short diagnosis order

First confirm the page and API origins. Then distinguish a direct request from OPTIONS followed by the actual method. Check allowed origin, method and headers on preflight; check origin permission on the application response. Finally inspect authentication and cookie policy.

Preflight caching can suppress an OPTIONS request that appeared earlier. Do not infer that a request became simple merely because no new OPTIONS entry appears. This sequence narrows the failure to a specific exchange without weakening API access controls.

Points à vérifier

  • Record the exact browser Origin, method and requested headers.
  • Inspect both OPTIONS and the actual response, including errors.
  • Use an explicit allowed origin for credentialed responses.
  • Keep authentication and request-forgery protections separate from CORS.

This guide covers common fetch requests. Redirects, cookie policy, network failures and application authentication can produce additional failures; CORS headers do not resolve them all.

Sources

  1. MDN: CORS ↗
  2. MDN: preflight request ↗
Retour en haut ↑