Yeni bir ön uç (frontend) yayınlarsınız, konsolu açarsınız ve işte orada: isteğin "CORS politikası tarafından engellendiğini" söyleyen kırmızı bir CORS hatası. API'niz Apidog veya curl'de sorunsuz çalışır, ancak tarayıcı yanıtı JavaScript'inize iletmeyi reddeder. Sinir bozucu mu? Evet. Gizemli mi? Hatayı nerede yaşadığını bir kez öğrendiğinizde değil.
Çoğu eğitimin göz ardı ettiği temel gerçek şu: CORS hatası tarayıcı tarafından uygulanır ancak sunucu tarafından kaynaklanır. Tarayıcı yanıtı engeller çünkü sunucunuz doğru Access-Control-Allow-Origin başlıklarını göndermedi. Bu nedenle düzeltme neredeyse her zaman sunucu yapılandırmasında yapılır, ön uç kodunuzda değil.
Bu rehber, CORS'un ne işe yaradığını, ön kontrol (preflight) isteğinin nasıl çalıştığını, her biri için kesin düzeltmeyle birlikte en yaygın altı CORS hata mesajını ve Express, Spring Boot ve Nginx için çalışan yapılandırmayı anlatır. Ayrıca, "sunucu yanlış yapılandırılmış" ile "tarayıcı engelledi" ayrımını yapmanın en hızlı yolu olan tarayıcı dışından nasıl hata ayıklayacağınızı da göreceksiniz.
CORS hatası nedir (ve ne değildir)
CORS, Kaynaklar Arası Kaynak Paylaşımı (Cross-Origin Resource Sharing) anlamına gelir. Varsayılan olarak, tarayıcılar aynı kaynak politikasını uygular: https://app.example.com adresinde çalışan JavaScript, https://api.example.com adresinden yanıtları okuyamaz, çünkü şema, ana bilgisayar veya bağlantı noktası farklıdır. CORS, sunucuların bu kuralı bilerek gevşetmek için kullandığı mekanizmadır. Tüm ayrıntılar MDN CORS belgelerinde bulunur ve temel algoritma Fetch spesifikasyonunda tanımlanmıştır.
Üç nokta, çoğu karışıklığı giderir:
- Tarayıcı uygular. Yalnızca tarayıcılar CORS kontrollerini uygular. Sunucudan sunucuya çağrılar, curl ve masaüstü API istemcileri bunu tamamen göz ardı eder.
- Sunucu yapılandırır. Tarayıcı, sunucunuzun gönderdiği yanıt başlıklarına göre karar verir. Başlık yoksa, erişim yok.
- İstek genellikle yine de sunucuya ulaşır. Basit istekler için sunucu her şeyi işler ve yanıt verir. Tarayıcı daha sonra yanıtı JavaScript'inizden gizler. CORS, API'nizin etrafında bir güvenlik duvarı değildir; kullanıcıları, çerezleriyle kaynaklar arası verileri okuyan kötü niyetli sayfalardan korur.
Bu nedenle bir CORS hatası gördüğünüzde, ön uçta geçici bir çözüme başvurmayın. Hata mesajını okuyun, ardından sunucudaki eksik veya yanlış başlığı düzeltin.
Ön kontrol isteğinin anatomisi
Belirli kaynaklar arası isteklerden önce, tarayıcı bir keşif isteği gönderir: ön kontrol (preflight) adı verilen bir OPTIONS isteği. Bu istek, talebiniz GET, HEAD veya POST dışındaki yöntemleri kullandığında, Authorization gibi özel başlıklar gönderdiğinde veya application/json gibi bir Content-Type kullandığında tetiklenir.
Ön kontrol isteği şöyle görünür:
OPTIONS /v1/orders HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
Tarayıcı soruyor: "app.example.com adresindeki bir sayfa, bu başlıklarla buraya POST yapmak istiyor. İzin verildi mi?" Doğru bir sunucu yanıtı:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Vary: Origin
Herhangi bir parça eksikse, tarayıcı gerçek isteği hiç tetiklenmeden iptal eder. API uç noktanız asla çalışmaz, günlükleriniz OPTIONS isteğinden başka bir şey göstermez ve konsol bir CORS hatası gösterir. Access-Control-Max-Age, tarayıcıya bu kararı önbelleğe almasını söyler (burada 86400 saniye), böylece tekrarlanan istekler ön kontrolü atlar.
Bu iki aşamalı dansı aklınızda bulundurun. Tüm CORS hata ayıklamasının yarısı tek bir soruya iner: ön kontrol başarısız mı oldu, yoksa asıl istek mi başarısız oldu?
En yaygın 6 CORS hatası ve her birini nasıl düzelteceğiniz
Tarayıcılar şaşırtıcı derecede hassas CORS hata mesajları yazar. Sizinkini aşağıdaki listeyle eşleştirin.
1. 'Access-Control-Allow-Origin' başlığı mevcut değil
Klasik olan. Sunucunuz hiç CORS başlığı olmayan bir yanıt gönderdi. Tarayıcının değerlendirecek hiçbir şeyi yoktu, bu yüzden erişimi engelledi.
Çözüm: Sunucuyu, belirli istek gönderen kaynakla veya genel, kimlik bilgisi gerektirmeyen API'ler için * ile Access-Control-Allow-Origin gönderecek şekilde yapılandırın:
Access-Control-Allow-Origin: https://app.example.com
Bir tuzak: hata yanıtları, başarılı yanıtlar bunları içerseler bile genellikle CORS başlıklarını atlar. API'niz 500 hatası döndürürse ve ara yazılım (middleware) yalnızca 200'leri süslerse, konsol gerçek sunucu hatası yerine bir CORS hatası gösterir. CORS başlıklarının 403 Yasak ve 500 sayfaları dahil her yanıta eklendiğinden emin olun.
2. Joker karakter '*' kimlik bilgileriyle kullanılamaz
Mesaj şöyle der: "İsteğin kimlik bilgileri modu 'include' olduğunda, 'Access-Control-Allow-Origin' başlığının değeri joker karakter '*' olmamalıdır."
Ön uç uygulamanız credentials: 'include' ile çerezler veya kimlik doğrulama başlıkları gönderir, ancak sunucu Access-Control-Allow-Origin: * ile yanıt verir. Fetch spesifikasyonu bu eşleşmeyi yasaklar; bir joker karakter artı kimlik bilgileri, internetteki herhangi bir sitenin kimliği doğrulanmış yanıtları okumasına izin verir.
Çözüm: Joker karakter yerine tam kaynağı yankılayın ve kimlik bilgileri başlığını ekleyin:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Gelen Origin değerini bir izin listesine göre doğrulamadan önce yankılamayın. Kimlik bilgileri etkinleştirilmiş keyfi kaynakları yansıtmak, tüm korumayı bozar.
3. Ön kontrol isteğine verilen yanıt erişim kontrolü denetimini geçmiyor
Sunucunuz OPTIONS isteğini hiç ele almadı. Belki de yol yalnızca POST'u tanımlar, bu nedenle OPTIONS 404 veya 405 döndürür. Belki bir kimlik doğrulama ara yazılımı (middleware) bunu 401 ile reddetti çünkü ön kontrol hiçbir belirteç (token) taşımaz (tarayıcılar ön kontrollere asla kimlik bilgisi eklemez).
Çözüm: OPTIONS isteğini açıkça ele alın ve kimlik doğrulama çalışmadan önce CORS başlıklarının tam kümesiyle bir 2xx yanıtı döndürün. Çoğu framework'te, CORS ara yazılımını (middleware) önce bağlamak sorunu çözer. Eğer bunu elle yazıyorsanız:
app.options('/v1/orders', (req, res) => {
res.set({
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Authorization, Content-Type'
});
res.sendStatus(204);
});
4. Başlık değeri sağlanan kaynağa eşit değil
Sunucu bir Access-Control-Allow-Origin başlığı gönderir, ancak yanlış kaynağı belirtir. Yaygın nedenler: http://localhost:5173 adresinden test yaparken sabit kodlanmış bir üretim kaynağı, http ile https arasındaki bir izin listesi karşılaştırmasının başarısız olması veya gereksiz bir sondaki eğik çizgi (https://app.example.com/ geçerli bir kaynak değeri değildir).
Çözüm: İsteğin Origin başlığını izin listenizle tam olarak karşılaştırın, eşleşmeyi yankılayın ve Vary: Origin gönderin, böylece önbellekler ve CDN'ler bir kaynağın başlığını başka bir kaynağa sunmaz:
const allowed = ['https://app.example.com', 'http://localhost:5173'];
if (allowed.includes(req.headers.origin)) {
res.set('Access-Control-Allow-Origin', req.headers.origin);
res.set('Vary', 'Origin');
}
5. İstek başlık alanı veya yöntemi izin verilmiyor
İki benzer mesaj: "İstek başlık alanı authorization, ön kontrol yanıtındaki Access-Control-Allow-Headers tarafından izin verilmiyor" ve "PUT yöntemi, Access-Control-Allow-Methods tarafından izin verilmiyor."
Ön kontrol başarılı oldu, ancak yanıtı isteğinizin ihtiyaçlarını karşılamadı. Bir Authorization başlığı veya bir X-Request-Id eklediniz ve sunucunun izin listesi bunu hiç belirtmedi.
Çözüm: Ön kontrol yanıtını, ön ucunuzun gönderdiği her başlığı ve yöntemi içerecek şekilde genişletin:
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-Id
Buradaki başlık adları büyük/küçük harfe duyarlı değildir. Yöntemler büyük/küçük harfe duyarlıdır ve büyük harflerle yazılmalıdır.
6. Ön kontrol isteği için yönlendirmeye izin verilmiyor
Ön kontrol, 301 veya 302 döndüren bir URL'ye ulaştı ve tarayıcılar ön kontrol sırasında yönlendirmeleri takip etmeyi reddeder. Tipik suçlular: http URL'sinin https'ye yönlendirilmesi, framework'ünüzün "yardımcı bir şekilde" yönlendirdiği eksik bir sondaki eğik çizgi veya bir ağ geçidinin /v1/orders'ı /v1/orders/'a yönlendirmesi.
Çözüm: Ön ucunuzu doğrudan nihai URL'ye yönlendirin. Baştan itibaren https kullanın, yönlendiricinizin sondaki eğik çizgi kuralına uyun ve uç noktanın 3xx yerine 2xx ile yanıt verip vermediğini kontrol etmek için manuel bir OPTIONS çağrısı ile onaylayın.
Sunucu yapılandırma örnekleri
İşte üç yaygın yığında doğru CORS kurulumu.
Express
Başlıkları elle yazmak yerine resmi cors ara yazılımını (middleware) kullanın:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({
origin: ['https://app.example.com', 'http://localhost:5173'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Authorization', 'Content-Type'],
credentials: true,
maxAge: 86400
}));
Kimlik doğrulama ara yazılımınızdan (auth middleware) önce bağlayın, böylece ön kontroller eksik belirteçler (token) nedeniyle asla reddedilmez. Python geliştiricileri, Flask uygulamaları için aynı başlık mantığını içeren Flask-CORS uzantısından aynı deseni alırlar.
Spring Boot
WebMvcConfigurer aracılığıyla küresel yapılandırma:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/v1/**")
.allowedOrigins("https://app.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type")
.allowCredentials(true)
.maxAge(86400);
}
}
Spring Security kullanıyor musunuz? Güvenlik filtre zincirinizde de .cors(Customizer.withDefaults()) çağrısı yapın, aksi takdirde güvenlik katmanı ön kontrolleri MVC yapılandırması görmeden önce engelleyecektir. Tüm seçenekler için Spring CORS belgelerine bakın.
Nginx
Nginx, uygulamanızın önündeki istekleri sonlandırdığında, ön kontrolleri kenarda yanıtlayın:
location /v1/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
add_header Access-Control-Max-Age 86400 always;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Vary "Origin" always;
proxy_pass http://backend;
}
always bayrağı önemlidir. Bu olmadan, Nginx 4xx ve 5xx yanıtlarında add_header yönergelerini bırakır, bu da her başarısız istekte birinci hatayı yeniden oluşturur. Ve CORS'u sahiplenecek bir katman seçin: hem Nginx hem de uygulamanız başlık eklerse, tarayıcılar Access-Control-Allow-Origin: *, * gibi yinelenenleri görür ve yanıtı reddeder.
Tarayıcı dışında Apidog ile CORS hata ayıklaması
Konsol hatası, tarayıcının bir şeyi engellediğini söyler. Sunucunun ne gönderdiğini söylemez. Gerçeği görmenin en hızlı yolu tarayıcıyı döngüden çıkarmaktır.
Apidog bir masaüstü API istemcisidir, bu nedenle istekleri tarayıcı CORS kontrollerine hiç tabi değildir. Bu size temiz bir deney sağlar: ön ucunuzun yaptığı aynı isteği Apidog'dan gönderin. Eğer orada başarılı olursa, API mantığınız iyidir ve sorun tamamen eksik CORS başlıklarıdır. Eğer orada da başarısız olursa, bir CORS kostümü giymiş sıradan bir API hatanız var demektir ve genel API test teknikleri uygulanır.
Apidog'da bir CORS hata ayıklama oturumu şöyle görünür:
- Gerçek isteği tekrar oynatın. Tarayıcınızın Ağ (Network) sekmesindeki başarısız isteği kopyalayın ve aynı yöntem, başlıklar ve gövde ile Apidog'da yeniden oluşturun. Durumu ve gövdeyi kontrol edin. Buradaki bir 500 hatası, CORS'un asla sorununuz olmadığını gösterir.
- Ön kontrolü manuel olarak test edin. Yeni bir istek oluşturun, yöntemi
OPTIONSolarak ayarlayın ve bir tarayıcının göndereceği başlıkları ekleyin:Origin: https://app.example.com,Access-Control-Request-Method: POSTveAccess-Control-Request-Headers: authorization, content-type. Gönderin. - Yanıt başlıklarını inceleyin. Yanıt bölmesinde
Access-Control-Allow-Origin,Access-Control-Allow-MethodsveAccess-Control-Allow-Headers'ı arayın. Her değeri ön ucunuzun ihtiyaç duyduğu şeyle karşılaştırın. Eksik bir başlık, yanlış bir kaynak veya bir 3xx durumu hemen göze çarpar, konsol tahminleri yapmaya gerek kalmaz. - Düzeltmeyi doğrulayın. Sunucu yapılandırmasını değiştirdikten sonra, aynı kaydedilmiş
OPTIONSisteğini yeniden gönderin ve başlıkların güncellendiğini izleyin. Ön uçları yeniden dağıtmaya, önbellek temizleme ritüellerine gerek yok.
Bu iş akışı, "API istemcimde çalışıyor, tarayıcıda başarısız oluyor" şeklindeki ebedi argümanı saniyeler içinde çözer; Postman CORS testi sorusunun arkasındaki aynı bilmece. İstemci çalışır çünkü CORS'u atlar. Tarayıcı başarısız olur çünkü sunucunuz sihirli kelimeleri söylememiştir. Apidog'u ücretsiz indirin ve düzenli uç nokta testlerinizin yanına OPTIONS isteğini kaydedin; gelecekteki CORS sorunları tek tıklamayla çözülür.
30 saniyelik bir CORS kontrol listesi
Hatayı bildirmeden önce bu listeyi gözden geçirin:
- Başarısız yanıt,
Access-Control-Allow-Originiçeriyor mu? - Değeri, sayfanızın kaynağıyla tam olarak eşleşiyor mu (şema, ana bilgisayar, port, sondaki eğik çizgi yok)?
- Çerezler veya kimlik doğrulama kullanıyor musunuz? Belirli bir kaynak artı
Access-Control-Allow-Credentials: true'yu onaylayın, asla*kullanmayın. OPTIONS, isteğinizi kapsayan yöntemler ve başlıklarla birlikte bir 2xx döndürüyor mu?- Ön kontrol URL'sinde herhangi bir yönlendirme var mı?
- Hata yanıtları (401, 403, 500), başarılı yanıtlarla aynı CORS başlıklarını taşıyor mu?
On vakadan dokuzunda, bu altı satırdan biri cevabınızdır. Bunu Apidog'da manuel bir OPTIONS isteğiyle doğrulayın, sunucu yapılandırmasını düzeltin ve geliştirmeye geri dönün.
SSS
Neden sadece tarayıcıda bir CORS hatası alıyorum?
Çünkü yalnızca tarayıcılar CORS'u uygular. Aynı kaynak politikası, kullanıcıları kimliği doğrulanmış verilerini okuyan kötü niyetli sayfalardan korur, bu nedenle tarayıcılar her kaynaklar arası yanıtta Access-Control-Allow-Origin'ı kontrol eder. curl, arka uç hizmetleri ve masaüstü istemcilerin böyle bir kuralı yoktur. Bir istek tarayıcı dışında her yerde başarılı oluyorsa, sunucunuz CORS başlıklarını eksik veya yanlış yapılandırmıştır; API'nin kendisi sağlıklıdır.
CORS Postman veya Apidog için geçerli mi?
Hayır. Postman ve Apidog, bir tarayıcı sanal alanında çalışan web sayfaları değil, masaüstü uygulamalarıdır, bu nedenle istekleri CORS'u tamamen atlar. Onları CORS hata ayıklaması için kullanışlı kılan da tam olarak budur: sunucunun ham yanıt başlıklarını tarayıcının filtrelemesi olmadan gösterirler. Postman CORS testi karışıklığı genellikle burada başlar; bir masaüstü istemcisindeki başarılı bir istek, tarayıcı davranışıyla ilgili hiçbir şeyi kanıtlamaz, ancak başarısız olan katmanı izole eder.
CORS hatası bir güvenlik özelliği mi yoksa bir hata mı?
Bir özellik. CORS hataları, tarayıcının işini yaptığını gösterir: sunucu onaylamadığı sürece kaynaklar arası yanıt verilerini betiklere (script) maruz bırakmayı reddeder. Tarayıcıda bayraklar veya uzantılarla CORS'u devre dışı bırakmak, her kullanıcı hala duvarla karşılaşırken semptomu sizin makinenizde gizler. Bunun yerine sunucu başlıklarını düzeltin.
Access-Control-Allow-Origin: * her yerde kullanabilir miyim?
Yalnızca çerez veya kimlik doğrulama içermeyen herkese açık, salt okunur API'ler için. Joker karakter, kimlik bilgileri dahil edildiğinde reddedilir ve verilerinizin web üzerindeki her kaynağa açık olduğunu duyurur. Kimlik doğrulamalı herhangi bir şey için, bir kaynak izin listesi tutun, eşleşen kaynağı yankılayın ve paylaşılan önbelleklerin yanıtları ayrı tutması için Vary: Origin gönderin.
