CORS Nedir?
Web geliştiriminde JS çağrıları gerçekleştiren her geliştirici hayatının belirli bir bölümünde aşağıdaki CORS hatası ile karşılaşmıştır:
Bu hatayı hemen stackoverflow‘da incelediğimizde, tarayıcının güvenlik kaynaklı nedenlerden dolayı ilgili isteği engellediğini görebiliyoruz. Peki CORS’un tam olarak işlevi ve amacı nedir?
CORS Nedir?
Cross-Origin Resource Sharing (Kökenler arası kaynak paylaşımı) anlamına gelen CORS, web tarayıcısı tarafından yönetilen ve ek HTTP başlıkları kullanılarak, bir kökende çalışan web uygulamasının, farklı bir kökende yer alan web uygulamasına erişim izni kontrolünü sağlayan mekanizmadır. Web uygulaması, internet tarayıcısı üzerinden farklı bir kökene (protokol, domain ve port) herhangi bir istek gönderirse cross-origin HTTP isteği oluşturmuş olur.
Örneğin, http://domain-a.com
üzerinde yer alan bir web uygulamasının JavaScript tarafından ajax isteği göndererek, http://domain-b.com
‘a erişmesi bir cross origin isteğidir.
Not: Eğer frontend (browser) tarafından değil de backend tarafından (Örneğin C# koduyla) domain-b.com’a erişseydi bu bir cross origin istek olmayacaktı. Buna yazının ilerleyen kısımlarında detaylı şekilde değineceğiz.
Peki neden hataya yol açıyor?
Günümüzdeki birçok modern internet tarayıcısı, JavaScript kodu üzerinden başlatılan HTTP isteklerini güvenlik nedenlerinden dolayı kısıtlar. Ajax istekleri, tarayıcı üzerinde gerçekleşirken same-origin policy‘i (aynı köken politikasını) izler. Bu nedenle, ajax isteği gerçekleştiren bir web sitesi, sadece kendi sitesi üzerindeki kaynaklara erişim sağlama yetkisi vardır. Eğer farklı bir siteye erişmek istiyorsa ilgili sitenin yer aldığı uygulamada CORS başlıklarının uygun şekilde ayarlanması gereklidir.
Same-Origin Policy neden var?
İnternet tarayıcısı, bir domain’e yapılan isteklerde, o domain ile ilgili cookie’leri de yapılan isteğe ilişkilendirir ve sunucuya iletir. Login işlemleri için kullanılan session cookie’leri buna güzel bir örnektir. Kullanıcı siteye giriş yaptıktan sonra oturum açılmış olur ve geri dönen session cookie bilgisi tarayıcıda saklanır. Sonraki isteklerde otomatik olarak session cookie bilgisi de sunucuya iletilir.
Normalde zararsız gibi görünen bu yöntem, kullanıcının kendi tarayıcısında açtığı kötü niyetli bir sitenin, mevcut session cookie bilgisini kullanarak arka planda ilgili siteye istek göndermesi ile faciaya yol açabilir.
Same-origin policy bu gibi güvenlik nedenlerinden dolayı oluşturulmuştur.
Origin dediğimizde neyi anlıyoruz?
CORS dendiğinde herkesin aklına domain’ler arası kaynak alışverişi gelir. Fakat origin biraz daha detaylıdır. İki origin’in birbiri ile aynı olabilmesi için protokol, host ve port bilgilerinin aynı olması gereklidir. Yani eğer iki adres bilgisi aynı olsa bile, farklı port değerlerine sahip ise same-origin olma durumundan çıkarlar.
Same-origin policy güvenliği sağlıyorsa CORS nedir?
Birçok kişi tarafından CORS bir güvenlik mekanizması gibi görünse de aslında tam tersini icra etmektedir. Same-origin policy güvenliği sağlarken CORS, istenen siteler için istisnai durumları oluşturmayı sağlar. SOP engeller, CORS ise izin verir. Bu nedenle, CORS için sitenin binevi dışarıya açılan kapısı gibi düşünebiliriz.
Hangi istek türleri için CORS ayarlanmalıdır?
- Ajax çağrıları adını verdiğimiz XMLHttpRequest ve fetch() API çağrıları,
- Web fontlarının kullanımı (CSS üzerinden @font-face ile yapılan çağrımlar)
- WebGL tarafından istek edilen görsel çağrıları
- Canvas’a
drawImage()
metodu kullanılarak çizilen görsel ve videolar.
CORS nasıl çalışır?
CORS’u açıklamak için öncelikle prefilight’ı (önceden kontrol için yapılan HTTP isteğini) tetiklemeyen ve tetikleyen istekler anlamına gelen simple requests (basit istekler) ve preflighted requests (önceden kontrollü istekler) terimlerini açıklamamız gerekiyor.
Basit istekler
Bu istek türü preflight isteğini tetiklemez ve tek bir HTTP isteği halinde gönderilir. Aşağıdaki özelliklerden herhangi birine sahip olması yeterlidir:
- GET, HEAD ve POST metodu kullanırlar,
- Fetch Spec’inde yasaklı başlıklar bölümünün haricindeki HTTP başlıklarına sahiptirler:
- Accept
- Accept-Language
- Content-Language
- Content-Type (bazı değerler haricinde)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- Content-Type başlığı için izin verilen değerler:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
Not: Burada bulunmayan application/json olan içerik türü direkt olarak preflight’ı tetikliyor.
Örneğin test-cors.org üzerinden jsonplaceholder‘a bir istek gönderirsek, tarayıcı tarafından Origin header’ına test-cors URL’inin otomatik olarak atandığını görebiliriz:
GET /posts HTTP/1.1 Host: https://jsonplaceholder.typicode.com Origin: https://www.test-cors.org ...
CORS ayarlaması yapılmış sunucu Origin header’ını kontrol eder. Eğer ilgili origin izni var ise Access-Control-Allow-Origin header’ına ilgili origini atar ve aşağıdaki gibi bir response döndürür:
HTTP/1.1 200 OK Access-Control-Allow-Origin: https://www.test-cors.org ...
Ek preflight isteği gerektiren istekler
Basit isteklerin aksine preflight gerektiren isteklerde, orijinal isteğin gönderilmesinden önce tarayıcı otomatik olarak OPTIONS metodu ile bir preflight isteği oluşturur ve bunu sunucuya gönderir. Bu istek sayesinde, origin’in ilgili isteği gerçekleştirmesi için izni olup olmadığı kontrol edilir. Bir isteğin preflight’ı tetikleyebilmesi için aşağıdaki özelliklerden herhangi birine sahip olması yeterlidir:
- PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH metodu ile gönderilmesi
- Fetch Spec’inde yasaklı başlıklar bölümünde yer alan HTTP başlıklardan birine sahip olması:
- Accept
- Accept-Language
- Content-Language
- Content-Type (bazı değerler haricinde)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- Content-Type başlığı için aşağıdaki değerlerin haricindeki değerler:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
Örneğin test-cors.org üzerinden jsonplaceholder‘a JSON olan POST isteği gönderirsek, tarayıcı bu isteğin Content-Type başlığına bakacak ve başlık değeri application/json olduğu için otomatik olarak bir OPTIONS isteği oluşturup aşağıdaki 3 parametre ile birlikte sunucuya iletecek:
- Origin: test-cors.org (isteği yapan URL)
- Access-Control-Request-Method: POST (HTTP isteğinde kullanılan metod)
- Access-Control-Request-Headers: Content-Type (HTTP isteğinde kullanılan başlıklar)
OPTIONS /posts HTTP/1.1 Host: https://jsonplaceholder.typicode.com Origin: https://www.test-cors.org Access-Control-Request-Method: POST Access-Control-Request-Headers: Content-Type ...
jsonplaceholder sunucusu ilgili isteğe izin verdiği için aşağıdaki gibi bir cevap dönecektir:
HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://www.test-cors.org Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: Content-Type
Tarayıcı, preflight isteği sonucundaki değerleri kontrol eder ve gerçek POST isteğini gönderir. Bu istek, daha önce anlattığımız basit istek gibidir ve Origin başlığını içerir. Cevapta da daha önce olduğu gibi Access-Control-Allow-Origin başlığı yer alır.
CORS implementasyonu nasıl yapılır?
CORS istekleri için sunucu taraflı yapılandırma işlemleri aslında oldukça basittir.
.NET Core uygulamalarında CORS ayarları
Microsoft.AspNetCore.Cors nuget paketini projeye dahil ettikten sonra Startup class’ı içerisinde ConfigureServicesmetodunda AddCors() fonksiyonunun çağrılması ile CORS servislerinin uygulama içerisine dahil edilmesi sağlanabilir:
public void ConfigureServices(IServiceCollection services) { services.AddCors(); }
Ara katmanda CORS’un aktifleştirilmesi
Startup.cs sınıfı içerisindeki Configure metodunda UseCors() fonksiyonu çağrılarak middleware için CORS yapılandırılası sağlanabilir. example.com origin’ine yönelik tüm HTTP başlıklarına izin vermek için aşağıdaki gibi CORS yapılandırması gerçekleştirilebilir:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { ... app.UseCors(builder => builder.WithOrigins("http://example.com") .AllowAnyHeader() ); ... }
Daha detaylı bilgi için microsoft sitesine göz atılabilir.
Java JAX-RS uygulamaları için CORS ayarları
JAX-RS uygulamalarında filter kullanılarak uygulama çapında CORS yapılandırması sağlanabilir:
@Provider public class CorsFilter implements ContainerResponseFilter { @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext ctx) throws IOException { ctx.getHeaders().add("Access-Control-Allow-Origin", "example.com"); ctx.getHeaders().add("Access-Control-Allow-Credentials", "true"); ctx.getHeaders().add("Access-Control-Allow-Headers","origin, content-type, accept, authorization"); ctx.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD"); } }
Güvenlik uyarısı
Access-Control-Allow-Origin başlığına * değeri vermek her domainden gelecek istekler için izin vermeyi sağlar. Geliştiriciler genellikle fazla uğraşmamak için bu başlığa * değeri verirler. Fakat bu durum, uygulamanıza herhangi bir domain’den CSRF gibi saldırılarının yapılabilmesine de olanak tanır.
Sonuç olarak
Eğer bir REST servis yazıyorsanız CORS’u da baz alarak şablon bir servis oluşturup, diğer projelerde bu servis üzerinde değişikler yaparak ilerlemek yararlı olacaktır.
Eğer sizin de CORS hakkında sorularınız veya görüşleriniz varsa yorum kısmından bize yazabilirsiniz. Bir sonraki yazımızda görüşmek üzere.