Tarayıcı konsolunda kırmızı bir CORS hatası gördüyseniz, frontend uygulamanız bir API'ye istek atıyor ama tarayıcı yanıtı size teslim etmeyi reddediyor demektir. Klasik mesaj şudur: Access to fetch at '...' from origin '...' has been blocked by CORS policy. İlk bakışta sunucu çökmüş gibi görünür; oysa istek genelde sunucuya ulaşır, yanıt da döner — tarayıcı sadece güvenlik kuralları gereği o yanıtı JavaScript'inize vermez. Bu yazıda hatanın gerçekte ne olduğunu, origin ve preflight kavramlarını ve doğru başlık ayarlarıyla sorunu kalıcı olarak nasıl çözeceğinizi anlatıyorum.
CORS hatası tam olarak nedir?
CORS, Cross-Origin Resource Sharing (Kaynaklar Arası Kaynak Paylaşımı) kelimelerinin kısaltmasıdır. Tarayıcılar, varsayılan olarak same-origin policy (aynı kaynak politikası) uygular: bir sayfadaki JavaScript yalnızca aynı origin'e ait yanıtları okuyabilir. Origin üç parçadan oluşur: protokol, alan adı ve port. Yani https://site.com ile http://site.com, https://api.site.com ya da https://site.com:8080 birbirinden farklı origin'lerdir.
Frontend'iniz http://localhost:3000 üzerinde, API'niz http://localhost:8000 üzerinde çalışıyorsa bu iki farklı origin'dir ve tarayıcı, sunucu açıkça izin vermedikçe yanıtı engeller. CORS işte bu izni vermenin standartlaştırılmış yoludur: sunucu, yanıtına özel başlıklar ekleyerek "bu origin'in benden veri okumasına izin veriyorum" der.
Önemli ayrım: hata istemcide değil, sunucuda çözülür
En sık yapılan yanlış anlama budur. CORS bir tarayıcı mekanizmasıdır ama izin sunucudan gelir. JavaScript tarafında fetch ayarlarını değiştirerek hatayı çözemezsiniz; çözüm, API'nin doğru yanıt başlıklarını göndermesidir. Anahtar başlık şudur:
Access-Control-Allow-Origin: https://site.com
Bu başlık yanıtta yoksa veya istek atan origin ile eşleşmiyorsa tarayıcı yanıtı bloklar. Postman ya da curl ile aynı isteği attığınızda sorun yaşamazsınız — çünkü onlar tarayıcı değildir ve same-origin policy uygulamazlar. Hatanın yalnızca tarayıcıda çıkması da bu yüzdendir.
Preflight (OPTIONS) isteği nedir?
Bazı isteklerden önce tarayıcı, asıl isteği göndermeden bir preflight isteği atar. Bu, OPTIONS metoduyla yapılan bir "izin sorma" isteğidir. Tarayıcı şunu sorar: "Bu origin'den, bu metotla, bu başlıklarla istek atabilir miyim?"
Preflight tetiklenir çünkü istek "basit" değildir. Bir istek; GET, POST veya HEAD dışında bir metot kullanıyorsa (örn. PUT, DELETE, PATCH), ya da özel başlıklar (örn. Authorization, Content-Type: application/json) içeriyorsa preflight gerekir. Sunucu, OPTIONS yanıtında izin verilen metot ve başlıkları döndürmelidir:
Access-Control-Allow-Origin: https://site.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Access-Control-Max-Age, preflight yanıtının kaç saniye önbelleğe alınacağını söyler; her istekte tekrar OPTIONS atılmasını önler. Eğer OPTIONS isteğine sunucu 404 ya da 405 dönüyorsa, çözmeniz gereken ilk şey budur.
CORS hatasını sunucu tarafında çözmek
Çözüm framework'e göre değişir ama mantık aynıdır: doğru başlıkları ekleyin. Express tabanlı bir Node.js API'sinde resmi cors paketi en temiz yoldur:
const cors = require('cors');
app.use(cors({
origin: 'https://site.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
}));
Laravel'de CORS yapılandırması config/cors.php dosyasındadır ve HandleCors middleware'i bunu otomatik uygular. İzin verilen origin'leri orada tanımlarsınız:
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['https://site.com'],
'allowed_headers' => ['*'],
'supports_credentials' => true,
Nginx ya da Apache gibi bir web sunucusunda başlıkları doğrudan da ekleyebilirsiniz, ama uygulama katmanında yönetmek genelde daha esnektir çünkü origin'i koşullu olarak doğrulayabilirsiniz.
Kimlik bilgileri (cookie ve token) ile çalışırken
İstekleriniz cookie veya kimlik doğrulama bilgisi taşıyorsa iki kural devreye girer. Birincisi: istemci tarafında credentials: 'include' ayarını açmalısınız:
fetch('https://api.site.com/data', {
credentials: 'include',
});
İkincisi ve en kritik nokta: credentials kullanıldığında Access-Control-Allow-Origin başlığı * (joker) olamaz. Tarayıcı bunu güvenlik gereği reddeder; tam origin'i açıkça yazmanız gerekir. Ayrıca sunucu Access-Control-Allow-Credentials: true başlığını da göndermelidir. Bu iki kuralı atlamak, "credentials ile wildcard kullanılamaz" benzeri kafa karıştırıcı bir hataya yol açan en yaygın sebeplerdendir.
Yaygın hatalar ve hızlı kontrol listesi
- Wildcard + credentials çakışması: Cookie gönderiyorsanız
*yerine tam origin yazın. - OPTIONS yanıtsız kalıyor: Sunucunuzun preflight'a 2xx yanıt verdiğinden emin olun.
- Trailing slash farkı:
https://site.comilehttps://site.com/origin eşleşmesinde sorun yaratabilir; protokol+host+port'u net karşılaştırın. - Proxy çözümü: Geliştirme ortamında frontend dev sunucusunu (örn. Vite) API'ye proxy'leyerek istekleri aynı origin'den çıkmış gibi gösterebilirsiniz.
Sık Sorulan Sorular
CORS hatasını sadece frontend kodunu değiştirerek çözebilir miyim?
Hayır. Doğru yanıt başlıkları sunucudan gelmek zorundadır. Frontend'de yapabileceğiniz tek şey, geliştirme ortamında bir proxy kullanmak ya da credentials ayarını düzeltmektir; asıl izin daima API tarafında verilir.
Neden Postman'de çalışıyor ama tarayıcıda CORS hatası alıyorum?
Çünkü CORS yalnızca tarayıcının uyguladığı bir güvenlik politikasıdır. Postman ve curl same-origin policy uygulamaz, bu yüzden aynı istek onlarda sorunsuz çalışır. Bu, sunucunun çalıştığını ama CORS başlıklarının eksik olduğunu gösterir.
Access-Control-Allow-Origin: * kullanmak güvenli mi?
Herkese açık, kimlik bilgisi taşımayan public bir API için kabul edilebilir. Ancak cookie/token ile çalışan veya hassas veri sunan API'lerde wildcard kullanmayın; izin verilen origin'leri açıkça listeleyin.
API'nizdeki CORS yapılandırmasını kalıcı ve güvenli biçimde çözmek mi istiyorsunuz? Frontend ile backend arasındaki origin, preflight ve kimlik bilgisi ayarlarını birlikte gözden geçirip projenize uygun bir çözüm kuralım — benimle iletişime geçin.