easyutils.io / Blog › CORS Errors, Demystified: A Step-by-Step Troubleshooting Guide

CORS Errors, Demystified: A Step-by-Step Troubleshooting Guide

Published: 2026-04-28 · 8 min read

CORS is not a security feature of your API. It is a browser feature that protects users from your API. Once you internalize that distinction, the error messages start making sense. Here is the framework I wish I'd had when I was debugging my first CORS issue at 11pm.

🌐
Test a CORS endpointSend a preflight from any origin, inspect headers, and see exactly why the browser is blocking your request.
Open tool →

What CORS actually does

By default, a browser will not let JavaScript loaded from origin A read responses from origin B. This is the same-origin policy. CORS is the standard way for origin B to say "actually, A is allowed to read me." The check happens entirely in the browser. Your server can't "fix CORS for everyone" — it can only opt in.

Three consequences follow:

  • CORS only affects browser-based clients. curl, mobile apps, server-to-server: no CORS.
  • If the response reaches the browser at all, your server did the work. CORS rejects happen after the request is sent.
  • "Disable CORS" doesn't exist on the server side. You can only widen what you allow.

Simple vs preflighted requests

The browser splits requests into two categories. A "simple" request — GET/HEAD/POST with a few standard content types and no custom headers — is sent directly, and CORS is checked on the response. A "preflighted" request triggers an OPTIONS call first with Access-Control-Request-Method and Access-Control-Request-Headers; the browser only sends the actual request if the preflight passes.

Things that trigger preflight:

  • Methods other than GET/HEAD/POST.
  • Content-Type outside application/x-www-form-urlencoded, multipart/form-data, or text/plain. This catches every application/json POST.
  • Custom headers (Authorization, X-Anything).

In practice: any modern fetch with a JSON body or an auth header preflights.

The five errors that cover 95% of cases

1. "No 'Access-Control-Allow-Origin' header is present"

Your server didn't reply with Access-Control-Allow-Origin at all. Either the request didn't reach your CORS middleware, or you only respond on the success path and forgot the error path (4xx/5xx responses still need CORS headers).

2. "The value of Allow-Origin must not be the wildcard '*' when credentials are 'include'"

If your fetch uses credentials: 'include' (or your axios sets withCredentials: true), the server cannot reply with Access-Control-Allow-Origin: *. It must echo the specific origin and also send Access-Control-Allow-Credentials: true.

3. "Method/Header X is not allowed by Access-Control-Allow-Methods/Headers"

The preflight succeeded but didn't list the method or header you're using. Make sure your CORS config includes the actual headers (case-insensitive) — including Authorization, Content-Type, and any custom ones.

4. "Response to preflight has invalid HTTP status code 4xx"

Your OPTIONS handler returned an error. Common causes: auth middleware rejecting unauthenticated OPTIONS (preflight requests don't carry the credentials yet — they're a separate exchange), or a router that doesn't have an OPTIONS handler registered.

5. "Request header field X is not allowed"

The preflight asked for header X, server replied without listing X in Access-Control-Allow-Headers. Fix the server-side allowlist.

The debugging order

  1. Reproduce with curl. If curl succeeds, the request itself is fine — the issue is purely CORS configuration. If curl fails, fix that first; CORS is a red herring.
  2. Open DevTools → Network → the failing request. Look for two lines: the OPTIONS preflight (if any) and the actual request. Both have their own status and headers.
  3. Check the OPTIONS response. It must return 2xx and include Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers.
  4. Check the main response. Even on a real GET/POST, it must include Access-Control-Allow-Origin.
  5. If using credentials: server must echo the exact origin (not wildcard) and send Access-Control-Allow-Credentials: true; client must send credentials: 'include'.

What not to do

  • Don't set Access-Control-Allow-Origin: * in production unless your API is genuinely public. Echo the request's origin against a server-side allowlist.
  • Don't proxy through your own backend just to dodge CORS for first-party APIs you control. Fix the config instead. (Proxy is fine when you can't change the upstream.)
  • Don't install browser extensions that disable CORS. They paper over real bugs that will bite users.
🌐
Test a CORS endpointSend a preflight from any origin, inspect headers, and see exactly why the browser is blocking your request.
Open tool →
easyutils.io / Blog › CORS Hataları: Adım Adım Çözüm Rehberi

CORS Hataları: Adım Adım Çözüm Rehberi

Yayın tarihi: 2026-04-28 · 8 dk okuma

CORS API'nizin güvenlik özelliği değildir. Tarayıcının, kullanıcıyı API'nizden koruyan özelliğidir. Bu ayrımı içselleştirdiğinizde hata mesajları anlamlı hâle gelir. İlk CORS sorununu gece 11'de çözmeye çalışırken yanımda olmasını istediğim çerçeve:

🌐
CORS endpoint'i test etİstediğin origin'den preflight gönder, header'ları incele ve tarayıcının isteği neden engellediğini gör.
Aracı aç →

CORS aslında ne yapar?

Tarayıcı varsayılan olarak, A origin'inden yüklenen JavaScript'in B origin'inden gelen cevapları okumasına izin vermez. Bu same-origin policy'dir. CORS, B origin'inin "aslında A'ya okuma izni veriyorum" demesinin standart yoludur. Kontrol tamamen tarayıcıda yapılır. Sunucunuz "herkes için CORS'u kapatamaz" — sadece neye izin verdiğini açabilir.

Üç sonuç:

  • CORS yalnızca tarayıcı tabanlı istemcileri etkiler. curl, mobil uygulamalar, server-to-server: CORS yok.
  • Cevap tarayıcıya ulaştıysa sunucu işini yapmış demektir. CORS reddi istek gönderildikten sonra oluşur.
  • "CORS'u kapat" sunucu tarafında diye bir şey yoktur. Sadece izin verdiğin alanı genişletebilirsin.

Basit vs preflighted istekler

Tarayıcı istekleri ikiye ayırır. "Basit" istek — GET/HEAD/POST + birkaç standart content-type + custom header yok — doğrudan gönderilir, CORS cevap üzerinde kontrol edilir. "Preflighted" istek önce Access-Control-Request-Method ve Access-Control-Request-Headers içeren bir OPTIONS çağrısı tetikler; tarayıcı yalnızca preflight geçtiğinde asıl isteği gönderir.

Preflight tetikleyenler:

  • GET/HEAD/POST dışındaki metotlar.
  • application/x-www-form-urlencoded, multipart/form-data veya text/plain dışındaki Content-Type. Bu, her application/json POST'u kapsar.
  • Custom header'lar (Authorization, X-Anything).

Pratikte: JSON body veya auth header'lı modern her fetch preflight'lar.

%95 vakayı kapsayan beş hata

1. "No 'Access-Control-Allow-Origin' header is present"

Sunucu hiç Access-Control-Allow-Origin dönmedi. Ya istek CORS middleware'ine ulaşmadı ya da yalnızca başarı yolunda dönüyorsunuz; hata yolunu (4xx/5xx) unuttunuz. Hata cevaplarının da CORS header'ları olmalı.

2. "Allow-Origin değeri credentials 'include' iken '*' olamaz"

Fetch credentials: 'include' kullanıyorsa (veya axios'ta withCredentials: true), sunucu Access-Control-Allow-Origin: * dönemez. Spesifik origin'i echo etmeli ve ek olarak Access-Control-Allow-Credentials: true göndermeli.

3. "Method/Header X is not allowed"

Preflight başarılı ama kullandığın method'u veya header'ı listelemiyor. CORS config'inde gerçek header'ları (case-insensitive) listele — Authorization, Content-Type ve custom'lar dahil.

4. "Response to preflight has invalid HTTP status code 4xx"

OPTIONS handler hata döndürdü. Yaygın sebepler: auth middleware'in OPTIONS'u reddetmesi (preflight'lar henüz credential taşımaz — ayrı bir takas), veya router'da OPTIONS handler'ın kayıtlı olmaması.

5. "Request header field X is not allowed"

Preflight X header'ını istedi, sunucu Access-Control-Allow-Headers'da X'i listelemedi. Sunucu allow-list'i düzelt.

Hata ayıklama sırası

  1. curl ile yeniden üret. curl başarılıysa istek kendisi sorunsuz — sorun saf CORS yapılandırması. curl başarısızsa önce onu düzelt; CORS bir kandırmacadır.
  2. DevTools → Network → başarısız istek. İki satır ara: OPTIONS preflight (varsa) ve asıl istek. Her ikisinin kendi status ve header'ları var.
  3. OPTIONS cevabını incele. 2xx dönmeli ve Access-Control-Allow-Origin, ...Methods, ...Headers içermeli.
  4. Asıl cevabı incele. Gerçek GET/POST cevabında da Access-Control-Allow-Origin olmalı.
  5. Credentials kullanıyorsan: sunucu tam origin'i echo etmeli (wildcard değil) ve Access-Control-Allow-Credentials: true göndermeli; istemci credentials: 'include' kullanmalı.

Yapmaman gerekenler

  • Üretimde Access-Control-Allow-Origin: * kullanma — API gerçekten public değilse. Server-side allow-list'e karşı origin'i echo et.
  • Kontrolündeki API'ler için sırf CORS'tan kaçmak için kendi backend'inden proxy yapma. Config'i düzelt. (Upstream'i değiştiremiyorsan proxy mantıklı.)
  • CORS kapatan tarayıcı eklentileri kurma. Gerçek bug'ları örter, kullanıcıyı vurur.
🌐
CORS endpoint'i test etİstediğin origin'den preflight gönder, header'ları incele ve tarayıcının isteği neden engellediğini gör.
Aracı aç →