Les pubs vous déplaisent ? Aller Sans pub Auj.

CORS Expliqué Why Your Fetch Works in curl but Explodes in the Browser

Mis à jour le

Les erreurs CORS ressentent comme des bugs du serveur. Ce n'est pas le cas. Ce sont des vérifications de permission imposées par le navigateur. Comprenez la politique d'origine identique, les requêtes préliminaires et les cinq en-têtes importants — et corrigez chaque erreur CORS en moins de cinq minutes.

CORS Explained: Why Your Fetch Works in curl but Explodes in the Browser 1
ANNONCE · Supprimer ?

You write a fetch() call. It works perfectly in curl. You open the browser, hit the endpoint, and get slapped with:

Access to fetch at 'https://api.example.com/data' from origin 'https://yourapp.com' 
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present 
on the requested resource.

You Google it. Stack Overflow tells you to add a header. You add the header. Different error. You add another header. Now it’s a preflight error. Two hours in, you’re disabling CORS entirely in Chrome with a flag you found in a 2016 answer. You ship it to production like that and hope nobody notices.

This article exists to stop that from happening. CORS takes about 10 minutes to understand correctly, and once you do, you fix it in 2 minutes every time.

It’s Not a Server Bug. It’s a Browser Rule.

This is the single most important thing to understand about CORS: it is enforced entirely by the browser. The server doesn’t block your request. The browser does, after the server already responded.

That’s why curl works. curl doesn’t enforce CORS. Neither does Postman. Neither does your backend calling another backend. CORS only activates when a browser makes a cross-origin request on behalf of a web page.

The server’s job is to say whether it permits cross-origin access via response headers. The browser’s job is to check those headers and decide whether to hand the response to your JavaScript. If the headers aren’t right, the browser discards the response and throws the error — even though the request completed and the server returned data.

Why the Browser Does This (The Same-Origin Policy)

The Same-Origin Policy (SOP) is a browser security rule that prevents JavaScript running on https://evil.com from reading responses from https://yourbank.com. Without it, any site could silently make authenticated requests on your behalf and read the results.

Two URLs have the same origin if their scheme, host, and port all match:

  • https://app.example.com et https://api.example.com — different origins (different subdomain)
  • http://example.com et https://example.com — different origins (different scheme)
  • https://example.com et https://example.com:8080 — different origins (different port)
  • https://example.com/foo et https://example.com/bar — same origin (path doesn’t count)

CORS is how servers opt in to relaxing SOP for specific origins. Not a bypass — an explicit permission grant.

Simple Requests vs. Preflight Requests

Not every cross-origin request triggers a preflight. The browser splits requests into two categories.

Requêtes simples go straight through (no OPTIONS check first). A request is “simple” when it meets all of:

  • Method is GET, POST, or HEAD
  • Content-Type is application/x-www-form-urlencoded, multipart/form-data, ou text/plain
  • No custom headers beyond the CORS-safelisted list

Preflighted requests are everything else — any PUT/PATCH/DELETE, any application/json body, any custom header like Authorization ou X-API-Key. Before the actual request, the browser automatically sends an OPTIONS request to ask “do you allow this?”

What a Preflight Actually Looks Like

Here’s a real preflight exchange. Your frontend calls fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' } }).

The browser first sends this OPTIONS request automatically — you never write this code:

OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: https://yourapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type

The server must respond with headers confirming it allows this:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400

Only if the preflight succeeds does the browser send the actual POST. If any preflight header is missing or wrong, the real request never leaves the browser.

Access-Control-Max-Age: 86400 caches the preflight result for 24 hours so the browser doesn’t repeat this handshake on every request. Worth setting it.

Les En-têtes qui Importent Vraiment

Access-Control-Allow-Origin — the only required header. Either a specific origin (https://yourapp.com) soit * for any origin. You cannot use * when sending credentials (cookies, Authorization headers).

Access-Control-Allow-Methods — required for preflighted requests. List every method you want to permit. Always include OPTIONS itself or some servers return 405 on the preflight.

Access-Control-Allow-Headers — required when the request includes custom headers. Must explicitly list every non-safelisted header your frontend sends. Missing Authorization here is responsible for roughly 40% of CORS support tickets.

Access-Control-Allow-Credentials: true — required only when you’re sending cookies or HTTP auth. Must also set credentials: 'include' on the fetch call. When this is true, Allow-Origin must be an explicit origin — * won’t work and you’ll get a second distinct error.

Access-Control-Expose-Headers — response headers your frontend can read. By default, JavaScript can only access a small safelisted set (Content-Type, Cache-Control, etc.). If you need to read a custom header like X-Request-Id in your response handler, add it here.

Five Mistakes That Cost Hours

1. Wildcard with credentials. Paramètre Access-Control-Allow-Origin: * and then wondering why credentialed requests still fail. The spec explicitly forbids wildcards when credentials are involved. You must reflect the specific origin: read the Origin request header and echo it back in the response.

2. Not handling OPTIONS at all. Express, FastAPI, and most frameworks don’t automatically respond to OPTIONS requests with CORS headers. If your CORS middleware only runs on actual endpoints and not on the preflight route, every credentialed or non-simple request will fail. The fix: make sure your CORS middleware runs before your router, or handle OPTIONS explicitly.

3. Returning CORS headers only on success. If your server returns a 4xx or 5xx without CORS headers, the browser hides the real status code from JavaScript. Your error handler receives a generic network error with no status. Always return Access-Control-Allow-Origin on every response, not just 200s.

4. Forgetting the port. https://app.example.com et https://app.example.com:3000 are different origins. During local development you’ll frequently hit a backend on port 8080 from a frontend on 3000. Both ports must be in your allowed origins list, or you need a dev proxy.

5. Caching a bad preflight. Access-Control-Max-Age caches the preflight. If you set it to 86400 and then update your allowed headers, browsers with the cached (wrong) preflight will keep failing for up to 24 hours. During development, either set this to 0 or clear it with a hard reload + DevTools cache disable.

Quick Fixes by Scenario

Frontend + backend on different domains, no cookies: Ajouter Access-Control-Allow-Origin: * (or the specific frontend origin) to your server. Done.

Frontend + backend on different domains, with cookies/auth: You need both Access-Control-Allow-Origin: https://yourfrontend.com (explicit, no wildcard) and Access-Control-Allow-Credentials: true on the server, plus credentials: 'include' in your fetch call. Cookies also need SameSite=None; Secure to cross origins.

Local dev hitting a remote API you don’t control: Add a dev proxy in your build tool. Vite: server.proxy. webpack-dev-server: devServer.proxy. Next.js: rewrites. The proxy makes your browser think all requests are same-origin; CORS never triggers.

API Gateway / CDN in front: Make sure the OPTIONS preflight isn’t getting swallowed before it reaches your origin. AWS API Gateway and CloudFront both have CORS settings that can conflict with your app-level headers — if you set headers in both places you’ll often get duplicated header values, which also fails.

If you need to verify the exact headers your server is returning, the Constructeur et validateur d'en-têtes CORS on IO Tools lets you construct and inspect the full header set without writing a single line of code. Useful for auditing what a backend actually returns vs. what you think it returns.

CORS errors look like server bugs because the error message mentions the server. They’re browser-enforced permission checks — and once you know which header is missing and why, every CORS error resolves in minutes. Build the right headers with the CORS Headers Builder or test your raw request structure with the Générateur de commande cURL to rule out client-side mistakes before touching server config.

Envie d'une expérience sans pub ? Passez à la version sans pub

Installez nos extensions

Ajoutez des outils IO à votre navigateur préféré pour un accès instantané et une recherche plus rapide

Sur Extension Chrome Sur Extension de bord Sur Extension Firefox Sur Extension de l'opéra

Le Tableau de Bord Est Arrivé !

Tableau de Bord est une façon amusante de suivre vos jeux, toutes les données sont stockées dans votre navigateur. D'autres fonctionnalités arrivent bientôt !

ANNONCE · Supprimer ?
ANNONCE · Supprimer ?
ANNONCE · Supprimer ?

Coin des nouvelles avec points forts techniques

Impliquez-vous

Aidez-nous à continuer à fournir des outils gratuits et précieux

Offre-moi un café
ANNONCE · Supprimer ?