Güvenlik & SSL

    CORS Nedir ve Güvenli Nasıl Yapılandırılır?

    CORS mekanizmasını, Access-Control başlıklarını ve güvenli yapılandırmayı yeni başlayanlara anlatan rehber.

    12 dk okuma Güncellendi: 10 Temmuz 2026

    Modern web uygulamalarının neredeyse tamamı artık tek bir sunucudan servis edilmiyor. Ön yüz app.ornek.com üzerinde çalışırken API api.ornek.com altında, statik dosyalar bir CDN'de, ödeme servisi bambaşka bir alan adında olabiliyor. Tarayıcı bu parçaların birbiriyle konuşmasına izin verirken çok net bir güvenlik kuralı uygular: bir sayfadaki JavaScript, kural olarak yalnızca kendi kaynağıyla (origin) serbestçe haberleşebilir; farklı bir kaynağa istek atmak isterse devreye "same-origin policy" (aynı kaynak politikası) girer ve bu isteğin cevabını okumasını engeller. CORS, yani Cross-Origin Resource Sharing, işte tam bu duvarın kontrollü bir kapısıdır. Sunucunun "bu kaynaktan gelen isteğe cevabımı okumana izin veriyorum" demesini sağlayan bir HTTP başlık protokolüdür.

    Sorun şu ki CORS çoğu geliştirici için bir güvenlik mekanizması olarak değil, konsolda gördüğü kırmızı bir hata olarak tanışılıyor. İnternette hızla bulunan çözüm de genellikle "her yere izin ver" anlamına gelen Access-Control-Allow-Origin: * satırını yapıştırmak oluyor. Bu satır hatayı susturur ama arka planda ciddi bir güvenlik açığının kapısını aralayabilir. Bu rehberde CORS'un neden var olduğunu, tarayıcının çapraz kaynak isteklerini nasıl denetlediğini, preflight (ön kontrol) akışının nasıl işlediğini ve en önemlisi yıldız (*) origin ile kimlik bilgilerini birlikte kullanmanın neden tehlikeli olduğunu; doğru allowlist yaklaşımının nasıl kurulacağını kıdemli bir sistem yöneticisinin gözünden anlatıyoruz.

    Origin Nedir ve Same-Origin Politikası Neyi Engeller?#

    CORS'u anlamak için önce "origin" (kaynak) kavramını netleştirmek gerekir. Bir origin üç parçadan oluşur: şema (protokol), host (alan adı) ve port. Bu üçünden herhangi biri farklıysa tarayıcı iki adresi farklı kaynak olarak görür.

    İstek yapan sayfaHedef adresAynı origin mi?Neden
    https://ornek.comhttps://ornek.com/apiEvetŞema, host, port aynı
    https://ornek.comhttp://ornek.comHayırŞema farklı (https ≠ http)
    https://ornek.comhttps://api.ornek.comHayırHost farklı (alt alan adı)
    https://ornek.comhttps://ornek.com:8443HayırPort farklı

    Same-origin policy, tarayıcının en temel güvenlik kurallarından biridir. Amacı basittir: bir web sitesinin JavaScript kodu, kullanıcının oturum açtığı başka bir siteye ait verileri sessizce okuyamasın. Bu politika olmasaydı, kötü niyetli bir sayfa arka planda banka.com/hesap-ozeti adresine istek atıp cevabını okuyabilir, oturumunuzu sömürebilirdi.

    Burada önemli bir ayrım var. Same-origin policy, çapraz kaynak isteğin gönderilmesini her zaman engellemez; asıl engellediği, JavaScript'in o isteğin cevabını okumasıdır. Örneğin bir HTML formu ya da img etiketi farklı bir origine istek atabilir (bu yüzden CSRF ayrı bir konudur), fakat fetch veya XMLHttpRequest ile atılan bir çapraz istekte, sunucu izin vermedikçe kodunuz gelen cevaba erişemez. CORS tam da bu izin mekanizmasıdır: sunucu, cevabına eklediği başlıklarla tarayıcıya "bu origine cevabımı gösterebilirsin" der.

    Basit İstekler ve CORS Başlıklarının Temeli#

    CORS akışının en sade hâli "basit istek" (simple request) senaryosudur. Bir istek şu koşulların hepsini sağlıyorsa tarayıcı ön kontrol yapmadan doğrudan gönderir: yöntem GET, POST ya da HEAD olmalı; sadece izin verilen basit başlıklar kullanılmalı; Content-Type yalnızca application/x-www-form-urlencoded, multipart/form-data veya text/plain değerlerinden biri olmalı.

    Böyle bir istekte tarayıcı otomatik olarak bir Origin başlığı ekler. Sunucu cevabında Access-Control-Allow-Origin başlığını döndürürse, tarayıcı gelen origin ile bu değeri karşılaştırır. Eşleşiyorsa JavaScript cevabı okuyabilir; eşleşmiyorsa tarayıcı cevabı bloklar ve konsola o meşhur CORS hatasını yazar.

    # Tarayıcının gönderdiği isteğe benzer bir çağrı
    curl -i https://api.ornek.com/urunler \
      -H "Origin: https://app.ornek.com"
    

    Sunucudan dönmesi beklenen cevap başlıkları şöyle görünür:

    HTTP/2 200
    content-type: application/json
    access-control-allow-origin: https://app.ornek.com
    vary: Origin
    

    En kritik CORS cevap başlıkları şunlardır:

    Başlıkİşlevi
    Access-Control-Allow-OriginCevabı okumasına izin verilen origin (* veya tek bir origin)
    Access-Control-Allow-CredentialsÇerez/kimlik bilgisi taşıyan isteklere izin verilip verilmediği (true)
    Access-Control-Allow-Methodsİzin verilen HTTP yöntemleri (preflight cevabında)
    Access-Control-Allow-Headersİstemcinin gönderebileceği özel başlıklar (preflight cevabında)
    Access-Control-Expose-HeadersJavaScript'in okuyabileceği ek cevap başlıkları
    Access-Control-Max-AgePreflight sonucunun kaç saniye önbelleğe alınacağı

    Burada Vary: Origin başlığına dikkat edin. Sunucu origin'e göre farklı Access-Control-Allow-Origin değeri döndürüyorsa, CDN ve tarayıcı önbelleğinin bu cevapları karıştırmaması için Vary: Origin şarttır. Aksi hâlde bir origine ait izinli cevap, başka bir origine yanlışlıkla servis edilebilir.

    Preflight (OPTIONS) Akışı Nasıl İşler?#

    İstek "basit" olma koşullarını karşılamıyorsa (örneğin PUT/DELETE yöntemi, Authorization başlığı veya application/json içeriği kullanıyorsa) tarayıcı asıl isteği göndermeden önce bir "ön kontrol" (preflight) yapar. Bu, OPTIONS yöntemiyle atılan otomatik bir sorudur: "Şu origine sahip bir sayfa, şu yöntem ve şu başlıklarla sana istek atmak istiyor; izin var mı?"

    Preflight isteği yaklaşık şöyle görünür:

    curl -i -X OPTIONS https://api.ornek.com/siparis \
      -H "Origin: https://app.ornek.com" \
      -H "Access-Control-Request-Method: POST" \
      -H "Access-Control-Request-Headers: content-type, authorization"
    

    Sunucu izin veriyorsa şuna benzer bir cevap döndürür:

    HTTP/2 204
    access-control-allow-origin: https://app.ornek.com
    access-control-allow-methods: GET, POST, PUT, DELETE, OPTIONS
    access-control-allow-headers: Content-Type, Authorization
    access-control-allow-credentials: true
    access-control-max-age: 600
    vary: Origin
    

    Tarayıcı bu cevabı görüp koşulların uygun olduğunu doğruladıktan sonra asıl POST isteğini gönderir. Access-Control-Max-Age sayesinde, tanımladığınız süre boyunca (burada 600 saniye) aynı istek için tekrar preflight yapılmaz; bu, gereksiz OPTIONS trafiğini azaltarak performansı iyileştirir. Yeni başlayanların sık düştüğü bir hata, sunucunun OPTIONS isteğini hiç yanıtlamaması ya da 200/204 yerine 405 döndürmesidir; bu durumda asıl istek hiç gönderilmez ve uygulama "sessizce" bozulur. Preflight'ın bir kimlik doğrulama adımı olmadığını da unutmayın: gerçek erişim kontrolünü yine de asıl isteğin arka planda yapması gerekir.

    Kimlik Bilgileri (Credentials) ve Tehlikeli Yıldız Kombinasyonu#

    CORS'un en çok yanlış anlaşılan ve en çok istismar edilen tarafı, kimlik bilgileriyle olan ilişkisidir. Varsayılan olarak çapraz kaynak fetch istekleri çerezleri ve Authorization başlığını taşımaz. Kimlik bilgisi taşıması için istemcinin bunu açıkça istemesi gerekir:

    # Tarayıcı tarafı — kimlik bilgili çapraz istek
    fetch:
      url: https://api.ornek.com/hesap
      options:
        credentials: include   # çerezleri/Authorization'ı gönderir
    

    Sunucu tarafında ise iki koşulun birlikte sağlanması gerekir: cevapta Access-Control-Allow-Credentials: true bulunmalı ve Access-Control-Allow-Origin belirli bir origin olmalıdır. İşte kritik güvenlik kuralı burada devreye girer:

    Access-Control-Allow-Origin: * ile Access-Control-Allow-Credentials: true birlikte kullanılamaz. Tarayıcı bu kombinasyonu reddeder.

    Bu bir kaza değil, kasıtlı bir korumadır. Eğer tarayıcı hem "herkese izin ver" (*) hem de "çerezleri taşı" demeyi kabul etseydi, internetteki herhangi bir kötü niyetli site, ziyaretçinin oturum açtığı API'ye onun çerezleriyle istek atıp özel verilerini okuyabilirdi. Bu yüzden kimlik bilgili isteklerde origin'i tek tek adıyla belirtmek zorundasınız.

    Asıl tehlike, geliştiricilerin bu kısıtı aşmak için "origin'i olduğu gibi yansıtma" (reflection) yöntemine başvurmasıdır. Aşağıdaki yaklaşım son derece risklidir:

    # TEHLİKELİ — gelen Origin'i körü körüne yansıtır
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Access-Control-Allow-Credentials true always;
    

    Bu yapılandırma, isteği kim yaparsa yapsın onun origin'ini aynen geri döndürür. Sonuç, * + credentials yasağını teknik olarak aşan ama pratikte ondan daha da tehlikeli bir durumdur: her origin, kullanıcının çerezleriyle API cevaplarını okuyabilir hâle gelir. Saldırgan, kurbanı zararlı bir sayfaya çekip arka planda credentials: include ile API'nize istek atar ve dönen özel veriyi (hesap bilgisi, e-posta, token) kendi sunucusuna sızdırır. Bu, kimlik hırsızlığından veri sızıntısına kadar giden bir zincirin başlangıcıdır. Bir de sık görülen null origin tuzağı vardır: bazı sandbox iframe'ler ve yerel dosyalar Origin: null gönderir; allowlist'e null eklemek saldırgana kolayca taklit edilebilecek bir kapı açar, bu yüzden asla izin verilmemelidir.

    Güvenli CORS Yapılandırması: Doğru Allowlist Yaklaşımı#

    Güvenli CORS'un özü tek bir cümlede özetlenir: origin'i asla körü körüne yansıtma; onu bilinen bir listeye karşı doğrula ve yalnızca eşleşirse yansıt. İzin verilen kaynakları açık bir allowlist (beyaz liste) olarak tutun, gelen Origin başlığını bu listeyle karşılaştırın ve yalnızca listede varsa cevaba ekleyin.

    Nginx tarafında güvenli bir örnek şöyle kurulur:

    # İzinli origin'leri bir map ile tanımla
    map $http_origin $cors_origin {
        default "";
        "https://app.ornek.com"     $http_origin;
        "https://panel.ornek.com"   $http_origin;
    }
    
    server {
        location /api/ {
            # Yalnızca listedeki origin yansıtılır; diğerleri boş kalır
            add_header Access-Control-Allow-Origin  $cors_origin always;
            add_header Access-Control-Allow-Credentials true always;
            add_header Vary Origin always;
    
            if ($request_method = OPTIONS) {
                add_header Access-Control-Allow-Origin  $cors_origin always;
                add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
                add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
                add_header Access-Control-Max-Age 600 always;
                add_header Content-Length 0;
                return 204;
            }
        }
    }
    

    Bu yaklaşımın avantajı, listede olmayan bir origin geldiğinde $cors_origin değişkeninin boş kalması ve tarayıcının izin başlığı görmeyip isteği bloklamasıdır. Nginx yapılandırmasının temelleriyle ilgili daha fazla ayrıntıya Nginx kurulumu rehberimizden ulaşabilirsiniz.

    Uygulama katmanında da aynı prensip geçerlidir. Bir yapılandırma dosyasında origin'leri saklayıp kodda doğrulamak temiz bir çözümdür:

    ; app config — izinli origin'ler
    [cors]
    allowed_origins = https://app.ornek.com, https://panel.ornek.com
    allow_credentials = true
    max_age = 600
    

    Sunucu kodunuz gelen Origin başlığını bu listeyle karşılaştırmalı; eşleşirse tek tek o origin'i döndürmeli, eşleşmezse hiçbir Access-Control-Allow-Origin başlığı eklememelidir. Regex ile origin doğrularken de çok dikkatli olun: ornek\.com$ gibi kaçışsız bir kalıp, ornekXcom ya da kotu-ornek.com gibi adresleri yanlışlıkla eşleştirebilir; nokta karakterini kaçırmayın ve joker alt alan adlarında .ornek\.com$ biçiminde kesin sınır kullanın.

    Güvenli bir CORS politikası için pratik kontrol listesi şu şekildedir:

    • Kimlik bilgili isteklerde asla * kullanma; origin'i açık listeye göre doğrula.
    • Gelen Origin'i körü körüne yansıtma; yalnızca allowlist'te varsa yansıt.
    • Access-Control-Allow-Methods ve -Headers alanlarını gereğinden geniş tutma; sadece kullandıklarını yaz.
    • Origin'e göre değişen cevaplarda Vary: Origin ekle.
    • null origin'e ve HTTP (http://) origin'lere üretimde izin verme.
    • Yönetim/iç API uçlarını CORS ile açmak yerine mümkünse ayrı bir ağ katmanında tut.

    CORS ile Sık Karıştırılan Kavramlar ve Doğru Sınır#

    CORS'u güvenlik mimarinizde doğru konumlandırmak için birkaç yanlış anlamayı düzeltmek gerekir. CORS bir "erişim kontrolü" mekanizması değildir; bir tarayıcı korumasıdır. Yani API'nizi yalnızca CORS'a dayanarak korumaya çalışırsanız savunmasız kalırsınız. curl, Postman veya sunucudan sunucuya yapılan çağrılar CORS başlıklarını hiç dikkate almaz; same-origin politikasını uygulayan taraf tarayıcıdır. Bir saldırgan, tarayıcı dışı bir araçla API'nize doğrudan istek atarsa CORS onu hiçbir şekilde durdurmaz.

    Bunun pratik sonucu şudur: kimlik doğrulama, yetkilendirme ve girdi denetimi her zaman sunucu tarafında yapılmalıdır. CORS yalnızca "hangi web sayfalarının tarayıcıdan cevabımı okuyabileceğini" sınırlar. Gerçek erişim kontrolü token doğrulaması, oturum yönetimi ve sahiplik kontrolleriyle sağlanır. Bu katmanlı savunmanın önemli bir parçası da uygulama katmanındaki zararlı istekleri filtrelemektir; bir web uygulama güvenlik duvarı (WAF) CORS'un kapsamadığı enjeksiyon ve otomatik saldırı denemelerini yakalar.

    CORS ile CSRF'i (Cross-Site Request Forgery) de karıştırmamak gerekir. CORS, çapraz kaynak isteğin cevabının okunmasını sınırlar; CSRF ise kullanıcının oturumunu kullanarak izinsiz eylem yaptırmayı hedefler. Bir form gönderimi CORS'a takılmadan gidebilir, dolayısıyla CSRF'e karşı ayrıca token tabanlı koruma ve SameSite çerez ayarları gerekir. Bu iki mekanizma birbirini tamamlar, biri diğerinin yerini tutmaz.

    Test, Hata Ayıklama ve Yaygın Sorunlar#

    CORS sorunlarını çözmenin en hızlı yolu tarayıcı konsolunu ve ağ (Network) sekmesini birlikte okumaktır. Konsoldaki mesaj çoğu zaman tam olarak neyin eksik olduğunu söyler: "No 'Access-Control-Allow-Origin' header is present" başlığın hiç dönmediğini, "credentials mode" uyarısı ise * + credentials çakışmasını işaret eder. Ağ sekmesinde asıl istekten önce ayrı bir OPTIONS satırı görüyorsanız preflight çalışıyor demektir; o satırın cevabındaki başlıklara bakarak sorunu daraltabilirsiniz.

    Komut satırından da preflight'ı canlıya çıkmadan test edebilirsiniz:

    # Preflight cevabındaki CORS başlıklarını incele
    curl -sS -o /dev/null -D - -X OPTIONS \
      https://api.ornek.com/siparis \
      -H "Origin: https://app.ornek.com" \
      -H "Access-Control-Request-Method: POST" \
      -H "Access-Control-Request-Headers: content-type" | grep -i "access-control"
    

    Sık karşılaşılan tuzaklar arasında şunlar öne çıkar: Nginx add_header direktifleri, if blokları veya hata sayfaları devreye girdiğinde always bayrağı olmadan kaybolabilir, bu yüzden CORS başlıklarına her zaman always ekleyin. Bir başka klasik hata, uygulama ile ters vekil (reverse proxy) katmanının aynı başlığı iki kez eklemesidir; tarayıcı Access-Control-Allow-Origin başlığını iki değerle görünce isteği reddeder, dolayısıyla başlığı yalnızca tek bir katmanda tanımlayın. HTTP'den HTTPS'e yapılan bir yönlendirme sırasında preflight'ın kaybolması, alt alan adı ile ana alan adının birbirine origin sanılması ve önbelleğe alınmış eski preflight cevaplarının yeni kuralları gölgelemesi de sık rastlanan durumlardır. Bir değişiklik sonrası beklenmedik davranış görüyorsanız Access-Control-Max-Age süresince eski preflight'ın önbellekte kalabileceğini hatırlayın.

    Sıkça Sorulan Sorular#

    Access-Control-Allow-Origin yıldız (*) kullanmak her zaman kötü müdür?#

    Hayır, her zaman kötü değildir ama bağlama bağlıdır. Kimlik bilgisi (çerez, Authorization) gerektirmeyen, tamamen herkese açık ve hassas veri döndürmeyen uçlarda (örneğin genel bir fiyat listesi veya statik JSON) * kullanmak makul ve pratiktir. Ancak istek çerez veya token taşıyorsa * hem tarayıcı tarafından reddedilir hem de güvenlik açısından sakıncalıdır; bu durumda origin'i açık bir allowlist ile tek tek doğrulamanız gerekir.

    CORS hatası alıyorum ama isteğim sunucuya ulaşıyor, neden?#

    Çünkü CORS asıl olarak isteğin gönderilmesini değil, cevabının JavaScript tarafından okunmasını engeller. Sunucunuz isteği alıp işleyebilir, hatta veritabanına yazabilir; fakat cevapta doğru Access-Control-Allow-Origin başlığı olmadığı için tarayıcı cevabı sizin kodunuzdan gizler ve konsola CORS hatası yazar. Bu yüzden özellikle POST gibi durum değiştiren isteklerde, hata almanıza rağmen işlemin sunucuda gerçekleşmiş olması şaşırtıcı değildir; çözüm sunucudan doğru başlıkları döndürmektir.

    Preflight isteği neden gönderiliyor ve nasıl azaltabilirim?#

    Tarayıcı, "basit istek" koşullarını aşan çağrılarda (örneğin PUT/DELETE, application/json içerik tipi veya Authorization gibi özel başlıklar) güvenlik için önce bir OPTIONS preflight'ı gönderir. Bunu tamamen kaldıramazsınız ama sunucuda Access-Control-Max-Age başlığı ile preflight cevabını önbelleğe aldırarak aynı istek için tekrar tekrar sorulmasını engelleyebilirsiniz. Ayrıca gereksiz özel başlıklardan kaçınmak da preflight sayısını azaltır.

    CORS API'mi tek başına güvende tutar mı?#

    Hayır, CORS bir erişim kontrolü mekanizması değildir. Yalnızca tarayıcı ortamındaki başka web sayfalarının cevabınızı okumasını sınırlar; curl, Postman veya sunucudan sunucuya yapılan çağrılar CORS başlıklarını hiç dikkate almaz. Gerçek güvenlik için sunucu tarafında kimlik doğrulama, yetkilendirme, sahiplik kontrolleri ve girdi denetimi yapmanız şarttır. CORS'u bu katmanlı savunmanın yalnızca bir parçası olarak düşünmelisiniz.

    Access-Control-Allow-Credentials true iken origin nasıl ayarlanmalı?#

    Kimlik bilgili isteklerde Access-Control-Allow-Origin mutlaka tek ve belirli bir origin olmalıdır; * kesinlikle kullanılamaz çünkü tarayıcı bu kombinasyonu reddeder. Doğru yol, gelen Origin başlığını bir allowlist ile karşılaştırıp yalnızca listede varsa aynen döndürmektir. Bu esnada Vary: Origin başlığını da eklemeniz gerekir, aksi hâlde önbellek katmanları bir kullanıcının izinli cevabını başka bir origine yanlışlıkla servis edebilir.

    Gelen Origin başlığını yansıtmak (reflection) neden riskli?#

    Çünkü gelen origin'i körü körüne geri döndürmek, pratikte "her origine izin ver" demektir ve üstüne bir de Access-Control-Allow-Credentials: true eklendiğinde * + credentials yasağını teknik olarak aşarak çok daha tehlikeli bir açık yaratır. Bu yapılandırmada herhangi bir kötü niyetli site, kurbanın çerezleriyle API'nize istek atıp dönen özel veriyi okuyabilir. Doğru yaklaşım origin'i asla doğrudan yansıtmamak, önce bilinen bir listeyle doğrulamak ve yalnızca eşleşiyorsa döndürmektir.

    Kapanış#

    CORS, ilk bakışta bir engel gibi görünse de aslında tarayıcının kullanıcılarınızı koruyan en temel savunmalarından biridir. Onu "hatayı susturmak için yıldız yapıştırılan bir ayar" olarak değil, kimin cevabınızı okuyabileceğini belirleyen bilinçli bir izin politikası olarak ele alırsanız hem güvenli hem de sorunsuz çalışan bir yapı kurarsınız. Özetle: kimlik bilgili isteklerde asla * kullanmayın, gelen origin'i körü körüne yansıtmayın, açık bir allowlist tutun, Vary: Origin eklemeyi unutmayın ve gerçek erişim kontrolünü her zaman sunucu tarafında yapın.

    Uygulamanızı bu katmanlı yaklaşımla güçlendirmek istiyorsanız Clou.TR altyapısı yanınızda. Yönetimli sunucu ve VDS çözümlerimizle CORS başlıklarını ve ters vekil yapılandırmasını dilediğiniz gibi kontrol edebilir, WAF ve DDoS koruma hizmetlerimizle uygulama katmanındaki saldırıları CORS'un ötesinde durdurabilir, SSL ile tüm çapraz kaynak trafiğinizi HTTPS üzerinden güvene alabilirsiniz. Ekstra yardıma ihtiyaç duyarsanız sunucu yönetimi hizmetimizle uzman ekibimiz yapılandırmayı sizin adınıza güvenli biçimde kurar. Güvenli ve hızlı bir web deneyimi için hosting paketlerimize göz atın.

    GüvenlikWebAPI

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.