IIS’te CORS Yapılandırması ve Cross-Origin İsteklerin Yönetimi
Üretim ortamında bir gün aniden API çağrıları çalışmayı bırakır. Frontend ekibi sizi arar, “dün çalışıyordu” derler. Tarayıcı konsoluna bakarsınız ve orada sizi bekleyen o meşhur hata: Access to XMLHttpRequest at 'https://api.sirket.com' from origin 'https://app.sirket.com' has been blocked by CORS policy. IIS üzerinde CORS yapılandırması, görünürde basit ama detaylarında sürprizlerle dolu bir konudur. Bu yazıda hem teorik altyapıyı hem de gerçek senaryolarda nasıl çözdüğümü paylaşacağım.
CORS Nedir ve IIS Neden Özel Dikkat İster?
CORS (Cross-Origin Resource Sharing), tarayıcıların farklı origin’lerden gelen HTTP isteklerini nasıl işleyeceğini belirleyen bir güvenlik mekanizmasıdır. Aynı protokol, alan adı ve port kombinasyonu “origin” olarak tanımlanır. https://app.sirket.com ile https://api.sirket.com farklı origin’lerdir. Tarayıcı, bu iki origin arasındaki isteklere varsayılan olarak izin vermez.
Apache veya Nginx’te CORS genellikle birkaç satırla halledilebilir. IIS ise farklı bir yapıya sahiptir. Header yönetimi, HTTP metot kısıtlamaları ve preflight istekleri IIS’te ayrı ayrı ele alınması gereken konulardır. Üstelik IIS 7.x, 8.x ve 10 arasında davranış farklılıkları mevcuttur. Yanlış yapılandırılmış bir CORS politikası ya güvenlik açığı oluşturur ya da uygulamanızı tamamen çalışmaz hale getirir.
Temel HTTP Header’ları ve Preflight Mekanizması
Devam etmeden önce birkaç kavramı netleştirelim:
Simple Request: GET, POST veya HEAD metoduyla yapılan ve standart header’lar içeren isteklerdir. Tarayıcı bu istekleri doğrudan gönderir, önce preflight yapmaz.
Preflight Request: PUT, DELETE, PATCH gibi metodlar veya özel header’lar içeren istekler için tarayıcı önce bir OPTIONS isteği gönderir. Sunucu bu OPTIONS isteğine uygun header’larla cevap vermezse asıl istek hiç gönderilmez.
Kritik CORS header’ları şunlardır:
- Access-Control-Allow-Origin: Hangi origin’lere izin verildiğini belirtir.
*veya spesifik bir origin değeri alır - Access-Control-Allow-Methods: İzin verilen HTTP metodları listesi
- Access-Control-Allow-Headers: İzin verilen özel header’lar
- Access-Control-Allow-Credentials: Cookie ve authorization header’larının cross-origin isteklerde iletilip iletilmeyeceği
- Access-Control-Max-Age: Preflight sonucunun kaç saniye cache’leneceği
- Access-Control-Expose-Headers: JavaScript’in okuyabileceği response header’ları
IIS’te CORS Yapılandırma Yöntemleri
IIS’te CORS yapılandırmak için üç temel yol vardır: web.config üzerinden header ekleme, URL Rewrite modülü kullanma ve IIS CORS modülü kurma. Her birinin avantaj ve dezavantajları vardır.
Yöntem 1: web.config ile Statik Header Ekleme
En basit ve en yaygın yöntem budur. Ancak önemli bir kısıtı vardır: tüm origin’lere izin verirsiniz ya da sadece tek bir origin’e. Dinamik origin kontrolü için bu yöntem yetersiz kalır.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="https://app.sirket.com" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
<add name="Access-Control-Allow-Headers" value="Content-Type, Authorization, X-Requested-With" />
<add name="Access-Control-Allow-Credentials" value="true" />
<add name="Access-Control-Max-Age" value="3600" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
Bu yapılandırmanın ciddi bir sorunu var: Access-Control-Allow-Credentials: true ile birlikte Access-Control-Allow-Origin: * kullanırsanız tarayıcı isteği reddeder. Credentials kullanacaksanız origin’i mutlaka spesifik belirtmelisiniz.
Yöntem 2: Preflight İsteklerini URL Rewrite ile Yönetme
Statik header eklemenin en büyük problemi OPTIONS isteklerini ele almamasıdır. IIS arka planda OPTIONS metodunu bazen uygulama koduna iletmeden işler, bazen de 405 Method Not Allowed döner. URL Rewrite modülü ile bunu düzgünce yönetebilirsiniz:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="CORS Preflight" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{REQUEST_METHOD}" pattern="^OPTIONS$" />
</conditions>
<action type="CustomResponse"
statusCode="200"
statusReason="OK"
statusDescription="OK" />
</rule>
</rules>
<outboundRules>
<rule name="Add CORS Headers">
<match serverVariable="RESPONSE_Access-Control-Allow-Origin"
pattern="^$" />
<conditions>
<add input="{HTTP_ORIGIN}" pattern="^https://(app|admin).sirket.com$" />
</conditions>
<action type="Rewrite" value="{HTTP_ORIGIN}" />
</rule>
</outboundRules>
</rewrite>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Methods"
value="GET, POST, PUT, DELETE, OPTIONS, PATCH" />
<add name="Access-Control-Allow-Headers"
value="Content-Type, Authorization, X-Requested-With, Accept" />
<add name="Access-Control-Allow-Credentials" value="true" />
<add name="Access-Control-Max-Age" value="7200" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
Bu yapılandırmada outbound rules ile gelen HTTP_ORIGIN header’ını regex ile kontrol edip dinamik olarak Access-Control-Allow-Origin değerini set ediyoruz. Birden fazla origin’e izin vermeniz gerektiğinde bu yaklaşım çok işe yarar.
Yöntem 3: IIS CORS Modülü (Windows Server 2019+)
Microsoft, IIS için ayrı bir CORS modülü yayımladı. Bu modül IIS Manager üzerinden de yapılandırılabiliyor. Önce modülü yükleyin:
# Web Platform Installer veya doğrudan MSI ile kurabilirsiniz
# Modülün kurulu olup olmadığını kontrol etmek için:
Get-WebConfiguration -Filter "system.webServer/cors" -PSPath "IIS:" |
Select-Object -ExpandProperty enabled
# IIS CORS modülünü etkinleştirmek için web.config yapılandırması:
# Aşağıdaki XML'i web.config'e ekleyin
<configuration>
<system.webServer>
<cors enabled="true" failUnlistedOrigins="true">
<add origin="https://app.sirket.com"
allowCredentials="true"
maxAge="7200">
<allowHeaders allowAllRequestedHeaders="true">
<add header="Authorization" />
<add header="Content-Type" />
</allowHeaders>
<allowMethods>
<add method="GET" />
<add method="POST" />
<add method="PUT" />
<add method="DELETE" />
<add method="OPTIONS" />
</allowMethods>
<exposeHeaders>
<add header="X-Custom-Header" />
</exposeHeaders>
</add>
<add origin="https://admin.sirket.com"
allowCredentials="true"
maxAge="3600">
<allowHeaders allowAllRequestedHeaders="true" />
<allowMethods>
<add method="GET" />
<add method="POST" />
</allowMethods>
</add>
</cors>
</system.webServer>
</configuration>
Bu modülün failUnlistedOrigins="true" özelliği önemlidir. Listede olmayan bir origin’den istek geldiğinde 403 döndürür. Production ortamında bu özelliği mutlaka aktif edin.
Gerçek Dünya Senaryoları
Senaryo 1: ASP.NET Web API + Angular SPA
Kurumsal projelerde en sık karşılaştığım senaryo bu. Angular uygulaması https://portal.sirket.com adresinde, API ise https://api.sirket.com adresinde barındırılıyor. Angular HTTP interceptor’ı Authorization header ekliyor, bu da preflight tetikliyor.
Bu senaryoda sadece customHeaders yeterli olmaz çünkü Authorization header credentials içeriyor ve her istek için preflight gidiyor. Şu yapılandırma işe yarar:
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<remove name="X-Powered-By" />
<add name="Access-Control-Allow-Origin" value="https://portal.sirket.com" />
<add name="Access-Control-Allow-Methods"
value="GET, POST, PUT, DELETE, OPTIONS, PATCH" />
<add name="Access-Control-Allow-Headers"
value="Accept, Content-Type, Authorization, X-Requested-With,
Cache-Control, Pragma" />
<add name="Access-Control-Allow-Credentials" value="true" />
<add name="Access-Control-Max-Age" value="86400" />
</customHeaders>
</httpProtocol>
<security>
<requestFiltering>
<verbs>
<add verb="OPTIONS" allowed="true" />
</verbs>
</requestFiltering>
</security>
</system.webServer>
</configuration>
requestFiltering içindeki OPTIONS verb’ün izin verilmiş olması kritik. Bazı IIS kurulumlarında OPTIONS varsayılan olarak bloklanmış gelir.
Senaryo 2: Birden Fazla Subdomain
Geliştirme, staging ve production ortamlarını aynı API’ye bağlamanız gerektiğinde veya birden fazla müşteriye hizmet veren bir SaaS yapısında şu yaklaşımı kullanıyorum:
// Global.asax veya Startup.cs içinde programatik yaklaşım
// Bu yaklaşım web.config yerine kod tarafında yönetim sağlar
protected void Application_BeginRequest(object sender, EventArgs e)
{
var allowedOrigins = new HashSet<string>
{
"https://app.sirket.com",
"https://staging.sirket.com",
"https://dev.sirket.com"
};
var origin = HttpContext.Current.Request.Headers["Origin"];
if (origin != null && allowedOrigins.Contains(origin))
{
HttpContext.Current.Response.Headers.Add(
"Access-Control-Allow-Origin", origin);
HttpContext.Current.Response.Headers.Add(
"Access-Control-Allow-Credentials", "true");
HttpContext.Current.Response.Headers.Add(
"Vary", "Origin");
}
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.Headers.Add(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS, PATCH");
HttpContext.Current.Response.Headers.Add(
"Access-Control-Allow-Headers",
"Content-Type, Authorization, X-Requested-With");
HttpContext.Current.Response.Headers.Add(
"Access-Control-Max-Age", "3600");
HttpContext.Current.Response.StatusCode = 200;
HttpContext.Current.Response.End();
}
}
Buradaki Vary: Origin header’ı özellikle önemli. CDN veya proxy arkasında çalışıyorsanız bu header olmadan farklı origin’lerden gelen istekler yanlış cache’lenmiş response alabilir.
Senaryo 3: IIS’te Çalışan WordPress veya PHP Uygulamaları
Windows Server’da PHP çalıştırıyorsanız (FastCGI üzerinden) ve WordPress REST API’si kullanıyorsanız, hem PHP hem de IIS seviyesinde header çakışmaları yaşanabilir. Şu kontrolleri yapın:
# Mevcut site için CORS header'larını test etmek
$headers = @{
"Origin" = "https://app.sirket.com"
"Access-Control-Request-Method" = "POST"
"Access-Control-Request-Headers" = "Content-Type, Authorization"
}
$response = Invoke-WebRequest -Uri "https://api.sirket.com/wp-json/wp/v2/posts" `
-Method OPTIONS `
-Headers $headers `
-UseBasicParsing
$response.Headers | Format-List
# IIS log'larında CORS hatalarını aramak için
Get-Content "C:inetpublogsLogFilesW3SVC1u_ex*.log" |
Where-Object { $_ -match "OPTIONS" } |
Select-Object -Last 50
Sık Yapılan Hatalar ve Çözümleri
Yıllar içinde gördüğüm en yaygın hatalar:
Header duplikasyonu: Hem web.config’de hem uygulama kodunda CORS header’ı set etmek. Tarayıcı birden fazla Access-Control-Allow-Origin header görünce isteği reddeder. Bunu tespit etmek için:
# Response header'larını kontrol et
curl -I -H "Origin: https://app.sirket.com" https://api.sirket.com/endpoint
# Eğer Access-Control-Allow-Origin iki kez görünüyorsa duplikasyon var demektir
Wildcard ve credentials çelişkisi: Daha önce bahsettiğim konu. * ile credentials birlikte kullanılamaz. Bunu enforce etmek için:
<!-- YANLIŞ: Bu kombinasyon tarayıcı tarafından reddedilir -->
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Credentials" value="true" />
<!-- DOĞRU: Spesifik origin belirtin -->
<add name="Access-Control-Allow-Origin" value="https://app.sirket.com" />
<add name="Access-Control-Allow-Credentials" value="true" />
Application Pool Identity sorunları: IIS Express ile lokal geliştirmede çalışan ama production IIS’te çalışmayan CORS yapılandırmaları genellikle Application Pool kimlik farklarından kaynaklanır. Özellikle Integrated Pipeline Mode kullanın:
# Application Pool pipeline modunu kontrol et
Import-Module WebAdministration
Get-Item "IIS:AppPoolsSirketApiPool" |
Select-Object -ExpandProperty managedPipelineMode
# Integrated olarak ayarlamak için
Set-ItemProperty "IIS:AppPoolsSirketApiPool" `
-Name managedPipelineMode `
-Value "Integrated"
Güvenlik Notları
CORS yapılandırmasında güvenliği ihmal etmek ciddi sonuçlar doğurabilir. Şu kurallara uyun:
- Production’da asla
Access-Control-Allow-Origin: *kullanmayın, özellikle kimlik doğrulama gerektiren endpoint’lerde - İzin verilen origin listesini en küçük küme olarak tutun. “Her ihtimale karşı” diye fazladan origin eklemeyin
Access-Control-Allow-Headersiçinde gerçekten ihtiyaç duyulmayan header’ları listelemeyinAccess-Control-Max-Agedeğerini makul tutun. 86400 saniye (1 gün) çoğu durumda yeterlidir, ama sık değişen politikalar için daha düşük değer tercih edin- Regex ile origin kontrolü yapıyorsanız pattern’inizi dikkatli yazın.
sirket.comyerinesirket.comkullanın, yoksasirketXcomgibi alan adları da eşleşir
Sonuç
IIS’te CORS yapılandırması, katmanları olan bir konudur. Web.config’e birkaç satır eklemek bazen yeterli gelir, ama çoğu üretim senaryosunda preflight yönetimi, dinamik origin kontrolü ve header duplikasyonunu önleme gibi detaylara dikkat etmek gerekir. Özellikle credentials kullanan uygulamalar için IIS CORS modülünü veya URL Rewrite tabanlı dinamik yaklaşımı tercih edin.
Bir CORS problemiyle karşılaştığınızda ilk adımınız her zaman tarayıcı Network sekmesinde OPTIONS isteğini incelemek olsun. Response header’larına bakın, eksik olan header’ı tespit edin ve o katmanda çözüm üretin. IIS Event Viewer ve IIS logları da sorun gidermede kıymetli kaynaklar. Üretim ortamında değişiklik yapmadan önce staging’de test etmeyi, özellikle failUnlistedOrigins gibi kırıcı değişiklikleri mutlaka kontrollü bir şekilde devreye almayı ihmal etmeyin.
