✋🏼🔥 Görselleştirilmiş CS: CORS

Gizem Korkmaz
7 min readNov 7, 2022

--

Bu yazı Lydia Hallie’nin CS Visualized: CORS adlı makalesinin bir çevirisidir.

Konsolunda şu koca kırmızı Access to fetched has been blocked by CORS policy hatasıyla karşılaşmak her geliştiricinin bir şekilde başına dert olmuştur! 😬 Her ne kadar bu hatadan hızlıca kurtulmanın bazı yolları olsa da bu sefer işleri olduğu gibi kabul etmeyelim. Bunun yerine CORS’un tam olarak ne yaptığına ve aslında neden bizim dostumuz olduğuna bir bakalım.

❗️Bu blog yazısında HTTP’nin temellerini anlatmayacağım. HTTP istekleri ve yanıtları hakkında daha detaylı bilgi edinmek istiyorsanız bir süre önce bu konu hakkında da kısa bir yazı paylaştım. Örneklerimde de HTTP/2 yerine HTTP/1.1 kullandım, bu CORS’u etkilemiyor.

Frontend tarafında çoğunlukla farklı bir yerden aldığımız datayı göstermek isteriz. Bunu gösterebilmemiz için önce tarayıcının server’a datayı çekmek için bir istek yollaması gerekir. Client, server’ın datayı ona gönderebilmesi için gerekli olan tüm bilgilerle beraber bir HTTP isteği yollar.

www.mywebsite.com adlı sitemiz için api.website.com’daki bir server’dan bazı kullanıcı bilgileri çekmeye çalıştığımızı düşünelim.

Harika! Server’a bir HTTP isteği attık ve ardından istediğimiz JSON datası ile beraber bir yanıt döndü.

Şimdi birebir aynı isteği farklı bir domain ile deneyelim. İsteği www.mywebsite.com yerine www.anotherdomain.com’daki bir web sitesinden yapacağız.

Bir dakika, nasıl yani? Tam olarak aynı isteği yolladık ancak bu sefer tarayıcı tuhaf bir hata gösteriyor?

Az önce CORS’u görev başında görmüş olduk! 💪🏼 Şimdi bu hatanın nasıl oluştuğunu ve tam olarak ne anlama geldiğine bir bakalım.

✋🏼 Same-Origin Policy

Web, same-origin policy adında bir ilkeyi zorunlu tutar. Varsayılan bir durum olarak, yalnızca isteğimiz ile aynı yerde bulunan kaynaklara ulaşabiliriz! Örneğin, https://mywebsite.com/image1.png’de bulunan bir görseli almakta hiçbir sıkıntı olmayacaktır.

Bir kaynak, farklı bir (sub)domain, protocol ya da port’ta bulunuyorsa cross-origin olur!

Harika, ancak neden “same-origin policy” diye bir şey var?

Same-origin policy diye bir şey olmadığını varsayalım ve yanlışlıkla teyzenizin Facebook’tan gönderdiği virüslü bir linke tıkladınız diyelim. Bu link sizi içinde bankanızın web sitesini gösteren bir iframe gömülü “kötü bir web sitesine” yönlendirdi ve cookieler aracılığıyla başarıyla giriş yapmanızı sağladı! 😬

Bu “kötü web sitesinin” geliştiricileri, sizin adınıza hesaplarına para göndermek için web sitesinin bu iframe’e erişmesini ve bankanızın web sitesinin DOM içeriğiyle etkileşime girmesini mümkün kıldı!

Same-Origin Policy yok

Evet… Bu çok büyük bir güvenlik riski! Kimsenin her şeye erişebilir olmasını istemeyiz. 😧

Neyse ki, same-origin policy burada bize yardımcı oluyor! Bu politika, yalnızca aynı kökenden (same-origin) olan kaynaklara erişebilmemizi sağlar.

Same-Origin Policy ile

Bu durumda,www.evilwebsite.com kaynağı www.bank.com'dan cross-origin kaynaklara erişmeye çalıştı! Same-origin policy bunun olmasını engelledi ve kötü web sitesinin geliştiricilerinin banka verilerimize erişememesini sağladı 🥳

Tamam, peki… Ama bunun CORS ile ne ilgisi var?

🔥 Client-side CORS

Same-origin policy yalnızca script dosyaları için geçerli olsa da, tarayıcılar bu ilkeyi JavaScript istekleri için “genişletmiştir”: varsayılan olarak, yalnızca aynı kökenden (same-origin) getirilen kaynaklara erişebiliriz!

Hmm, bizim sık sık cross-origin kaynaklara da erişmemiz gerekiyor ama 🤔 Belki verileri yüklemek için frontend’in backend API’ı ile etkileşime girmesi gerekiyor? İşte bu yüzden tarayıcı, cross-origin isteklere güvenli bir şekilde izin vermek için CORS adlı bir mekanizma kullanır! 🥳

CORS, Cross-Origin Resource Sharing (Kökenler Arası Kaynak Paylaşımı) anlamına gelir. Tarayıcı aynı kökende bulunmayan kaynaklara erişmemize izin vermese de, bir yandan bu kaynaklara güvenli bir şekilde eriştiğimizden emin olurken bir yandan da bu güvenlik kısıtlamalarını biraz değiştirmek için CORS’u kullanabiliriz 🎉

Kullanıcı aracıları (bir tarayıcı gibi), HTTP response’undaki CORS’a özgü başlıkların değerlerine bağlı olarak, normal şartlar altında engellenecek olan cross-origin isteklerine izin vermek için CORS mekanizmasını kullanabilir! ✅

Cross-origin bir istek yapıldığında, client HTTP isteğimize otomatik olarak fazladan bir header daha ekler: Origin. Origin header’ının değeri, isteğin geldiği kaynaktır!

Tarayıcının cross-origin kaynaklara erişime izin vermesi için server yanıtından, bu server’ın cross-origin isteklere izin verip vermediğini belirten belirli headerlar bekler!

💻 Server-side CORS

Bir server geliştiricisi olarak, HTTP yanıtına her biri Access-Control-* ile başlayan ekstra başlıklar ekleyerek cross-origin isteklere izin verildiğinden emin olabiliriz. 🔥 Bu CORS response başlıklarının değerlerine bağlı olarak, tarayıcı artık same-origin policy tarafından engellenecek olan belirli cross-origin yanıtlara izin verebilir!

Kullanabileceğimiz birkaç CORS başlığı olmasına rağmen, tarayıcının cross-origin erişimine izin vermek için ihtiyaç duyduğu tek bir başlık vardır: Access-Control-Allow-Origin!
Bu başlığın değeri, hangi kökenlerin server’dan talep ettikleri kaynaklara erişmesine izin verildiğini belirtir.

https://mywebsite.com'un erişmesi gereken bir server’ı geliştiriyorsak, o domain değerini Access-Control-Allow-Origin başlığına ekleyebiliriz!

Mükemmel! 🎉 Bu başlık artık server’ın client’a gönderdiği yanıta eklendi. Bu başlığı ekleyerek, isteği https://mywebsite.com'dan gönderdiğimiz takdirde, same-origin policy https://api.mywebsite.com kaynağında bulunan verileri almamızı kısıtlamayacak!

Tarayıcı içindeki CORS mekanizması, Access-Control-Allow-Origin header değerinin, istek tarafından gönderilen Origin değerine eşit olup olmadığını kontrol eder 🤚🏼

Mevcut durumda isteğimizin kökeni Access-Control-Allow-Origin response başlığında listelenen https://mywebsite.com'dur!

Mükemmel! 🎉 Cross-origin kaynakları başarıyla alabildik! Peki, Access-Control-Allow-Origin başlığında listelenmeyen bir kökenden bu kaynaklara erişmeye çalıştığımızda ne olur? 🤔

Ahh evet, CORS bazen çok sinir bozucu olan şu meşhur korkunç hatayı gönderir! Şimdi bakınca bunun aslında gayet mantıklı olduğunu görebiliyoruz.

The 'Access-Control-Allow-Origin' header has a value
'https://www.mywebsite.com' that is not equal
to the supplied origin.

Yukarıdaki durumda, kaynak https://www.anotherwebsite.com idi. Ancak server’da Access-Control-Allow-Origin başlığındaki izin verilen kökenler listesinde bu sağlanan köken yok! CORS, isteği başarıyla engelledi ve böylece kodumuzda getirilen verilere erişemiyoruz 😃

CORS izin verilen köken listesi için joker * karakterini eklememize izin verir. Bu, tüm kökenlerden gelen isteklerin istenen kaynaklara erişmesi gerektiği anlamına gelir, bu yüzden dikkatli olun!

Access-Control-Allow-Origin sağlayabileceğimiz birçok CORS başlığından sadece bir tanesidir. Bir server geliştiricisi, belirli isteklere izin vermek (ya da iptal etmek) için sunucunun CORS politikalarını genişletebilir! 💪🏼

Diğer bir yaygın başlık, Access-Control-Allow-Methods başlığıdır! CORS, yalnızca listelenen yöntemlerle gönderilmişse cross-origin isteklerine izin verecektir.

Bu durumda, yalnızca GET, POST veya PUT yöntemine sahip isteklere izin verilecektir! PATCH ya da DELETEgibi diğer yöntemler engellenecektir ❌

Diğer olası CORS başlıklarının ne olduğunu ve ne için kullanıldığını merak ediyorsanız, bu listeye göz atın.

PUT, PATCH ve DELETE isteklerinden bahsetmişken, CORS aslında bu istekleri farklı şekilde ele alır! 🙃 Bu “basit olmayan” istekler, preflight request adı verilen bir şeyi başlatır!

🚀 Preflighted Requests

CORS’un iki tür isteği vardır; basit bir istek ve preflighted (önceden kontrol edilmiş) bir istek. Bir isteğin basit mi yoksa preflighted mı olduğu, istek içindeki bazı değerlere bağlıdır (endişelenmeyin, bunu ezberlemenize gerek yok :D).

İstek bir GET ya da POST yöntemi olduğunda ve herhangi özel bir header’a sahip olmadığında basittir! PUT, PATCH ya da DELETE yöntemiyle yapılan istekler gibi diğer tüm istekler önceden kontrol edilecektir.

Bir isteğin basit bir istek olabilmesi için hangi gereksinimleri karşılaması gerektiğini merak ediyorsanız, MDN’in faydalı bir listesi var!

Tamam ama bu preflighted istek ne anlama geliyor ve neden böyle bir şey oluyor?

Asıl istek gönderilmeden önce client, preflighted bir istek oluşturur! Preflighted istek, Access-Control-Request-* başlıklarında yapmak üzere olduğumuz gerçek istek hakkında bilgi içerir 🔥

Bu, sunucuya, tarayıcının yapmaya çalıştığı gerçek istek hakkında bilgi verir: isteğin yöntemi nedir, ek başlıklar nelerdir, vb.

Server bu preflighted isteği alır ve CORS başlıklarıyla birlikte boş bir HTTP yanıtı gönderir! Tarayıcı, CORS başlıkları dışında hiçbir veri içermeyen preflight yanıtını alır ve HTTP isteğine izin verilip verilmeyeceğini kontrol eder! ✅

Durum buysa, tarayıcı asıl isteği server’a gönderir ve server istediğimiz verilerle yanıt döner!

Ancak durum böyle değilse, CORS preflighted isteği engeller ve asıl istek asla gönderilmez ✋🏼 Preflighted istekler, herhangi bir CORS politikasının (henüz) etkin olmadığı serverlardaki kaynaklara erişmemizin veya kaynakları değiştirmemizi engellemenin harika bir yoludur! Serverlar artık potansiyel olarak istenmeyen cross-origin isteklerinden korunuyor 😃

💡 Server’a gidiş-dönüş sayısını azaltmak için, CORS taleplerimize bir Access-Control-Max-Age başlığı ekleyerek önceden kontrol edilmiş yanıtları önbelleğe alabiliriz (caching)! Preflighted bir yanıtı bu şekilde önbelleğe alabiliriz ve tarayıcı yeni bir preflighted istek göndermek yerine bunu kullanabilir!

🍪 Credentials

Çerezler, authorization başlıkları ve TLS sertifikaları varsayılan olarak yalnızca same-origin isteklerde ayarlanır! Ancak, bu kimlik bilgilerini (credentials) cross-origin isteğimizde de kullanmak isteyebiliriz. Belki de kullanıcıyı tanımlamak için server’ın kullanabileceği isteğe çerezler eklemek istiyoruzdur!

CORS varsayılan olarak credentials’ı içermese de, bunu Access-Control-Allow-Credentials CORS başlığını ekleyerek değiştirebiliriz! 🎉

Cross-origin isteğimize çerezleri ve diğer authorization başlıklarını dahil etmek istiyorsak, istekte withCredentialsalanını true olarak ayarlamamız ve yanıta Access-Control-Allow-Credentials başlığını eklememiz gerekir.

Her şey hazır! Artık cross-origin isteğimize credentials’ı da dahil edebiliriz 🥳

CORS hatalarının zaman zaman sinir bozucu olabileceği konusunda hemfikir olabileceğimizi düşünmeme rağmen, tarayıcıda güvenli bir şekilde cross-origin istekleri yapmamızı sağlaması inanılmaz (hatta biraz da sevgiyi hak ediyor 😆)

Açıkçası, same-origin policy ve CORS hakkında bu blog yazısında ele alabildiğimden çok daha fazlası var! Neyse ki, bu konuda daha fazlasını okumak istiyorsanız bu ve W3 spesifikasyonu gibi birçok harika kaynak var. 💪🏼

Ve her zaman olduğu gibi, bana ulaşmaktan çekinmeyin! 😊

--

--