Yeni bir frontend yayınlarsınız, konsolu açarsınız ve karşınızda kırmızı bir CORS hatası belirir: İstek “CORS politikası tarafından engellendi.” API, Apidog veya curl ile sorunsuz çalışırken tarayıcı yanıtı JavaScript'e iletmeyi reddeder. Sorunun kaynağını öğrendiğinizde bu durum gizemli olmaktan çıkar.
Temel gerçek şudur: CORS hatası tarayıcı tarafından uygulanır, ancak sunucu tarafından kaynaklanır. Sunucunuz doğru Access-Control-Allow-Origin başlığını göndermediğinde tarayıcı yanıtı engeller. Bu nedenle çözüm çoğunlukla frontend kodunda değil, sunucu yapılandırmasındadır.
Bu rehberde CORS'un ne olduğunu, preflight isteğinin nasıl çalıştığını, en yaygın altı hata mesajını ve Express, Spring Boot ve Nginx yapılandırmalarını inceleyeceğiz. Ayrıca tarayıcı dışından hata ayıklama yöntemini de göreceğiz.
CORS hatası nedir?
CORS, Cross-Origin Resource Sharing (Kaynaklar Arası Kaynak Paylaşımı) anlamına gelir.
Tarayıcılar varsayılan olarak aynı kaynak politikasını uygular. Örneğin https://app.example.com üzerinde çalışan JavaScript, şema, ana bilgisayar veya port farklı olduğu için https://api.example.com yanıtlarını okuyamaz.
CORS, sunucuların bu kuralı kontrollü biçimde gevşetmesini sağlayan mekanizmadır. Ayrıntılar için MDN CORS belgelerine ve Fetch spesifikasyonuna bakabilirsiniz.
Çoğu karışıklığı şu üç nokta çözer:
-
Tarayıcı uygular: CORS kontrollerini yalnızca tarayıcılar yapar. Sunucudan sunucuya çağrılar,
curlve masaüstü API istemcileri CORS'u yok sayar. - Sunucu yapılandırır: Tarayıcı kararını sunucunun gönderdiği yanıt başlıklarına göre verir.
- İstek sunucuya ulaşabilir: Basit isteklerde sunucu isteği işler ve yanıt verir; tarayıcı daha sonra bu yanıtı JavaScript'ten gizler.
CORS, API'nizin etrafında bir güvenlik duvarı değildir. Kullanıcıları, çerezleriyle kaynaklar arası verileri okumaya çalışan kötü amaçlı web sayfalarından korur.
Preflight isteği nasıl çalışır?
Bazı kaynaklar arası isteklerden önce tarayıcı bir keşif isteği gönderir. Buna preflight veya ön kontrol isteği denir. Bu istek OPTIONS yöntemiyle yapılır.
Preflight genellikle şu durumlarda tetiklenir:
-
GET,HEADveyaPOSTdışındaki bir yöntem kullanıldığında -
Authorizationgibi özel başlıklar gönderildiğinde -
application/jsongibi basit olmayan birContent-Typekullanıldığında
Örnek bir preflight isteği:
OPTIONS /v1/orders HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
Tarayıcı aslında şunu sorar:
https://app.example.comkaynağındaki bir sayfa, bu başlıklarlaPOSTisteği gönderebilir mi?
Sunucunun doğru yanıtı şöyle olabilir:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Vary: Origin
Herhangi bir parça eksikse tarayıcı gerçek isteği göndermeden işlemi iptal eder. API uç noktanız çalışmaz; günlüklerde yalnızca OPTIONS isteğini görürsünüz.
Access-Control-Max-Age, tarayıcıya preflight kararını önbelleğe almasını söyler. Yukarıdaki örnekte süre 86400 saniyedir.
CORS hata ayıklarken ilk sorunuz şu olmalı:
Preflight mı başarısız oldu, yoksa gerçek istek mi?
En yaygın 6 CORS hatası
1. 'Access-Control-Allow-Origin' header is missing
Sunucu yanıtında hiç CORS başlığı yoktur. Tarayıcı değerlendirecek bir bilgi bulamadığı için yanıtı engeller.
Belirli bir frontend kaynağı için:
Access-Control-Allow-Origin: https://app.example.com
Kimlik bilgisi gerektirmeyen herkese açık API'lerde * kullanılabilir.
Bir başka yaygın sorun da hata yanıtlarının CORS başlıklarını içermemesidir. Örneğin başarılı yanıtlar başlıkları içerirken 403 veya 500 yanıtları içermeyebilir. 403 Forbidden dahil olmak üzere tüm yanıtların CORS başlıklarını taşıdığından emin olun.
2. '*' wildcard cannot be used with credentials
Frontend çerez veya kimlik doğrulama bilgileri gönderiyor:
fetch(url, { credentials: 'include' });
Ancak sunucu şu yanıtı veriyor:
Access-Control-Allow-Origin: *
Kimlik bilgileriyle birlikte joker karakter kullanılamaz. Bunun yerine izin verilen kaynağı açıkça belirtin:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Gelen Origin değerini doğrulamadan doğrudan yansıtmayın. Kimlik bilgileri açıkken rastgele kaynakları yansıtmak CORS korumasını ortadan kaldırır.
3. Preflight yanıtı erişim kontrolü denetimini geçemiyor
Sunucu OPTIONS isteğini ele almıyor olabilir:
- Uç nokta yalnızca
POSTtanımlıyordur veOPTIONSiçin404veya405dönüyordur. - Kimlik doğrulama ara yazılımı, belirteç taşımayan preflight isteğini
401ile reddediyordur.
Preflight isteğini kimlik doğrulamadan önce işleyin ve gerekli başlıklarla birlikte 2xx yanıt döndürün:
app.options('/v1/orders', (req, res) => {
res.set({
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Authorization, Content-Type'
});
res.sendStatus(204);
});
Çoğu framework'te CORS ara yazılımını kimlik doğrulama ara yazılımından önce bağlamak yeterlidir. Express için resmi cors ara yazılımı belgelerine bakabilirsiniz.
4. Başlık değeri sağlanan kaynağa eşit değil
Sunucu Access-Control-Allow-Origin gönderir, ancak yanlış kaynağı belirtir.
Yaygın nedenler:
-
http://localhost:5173üzerinden test edilirken yalnızca üretim kaynağının tanımlı olması -
httpvehttpsarasındaki fark - Gereksiz sondaki eğik çizgi:
https://app.example.com/
Kaynağı izin listesiyle tam olarak karşılaştırın, eşleşen değeri yansıtın ve Vary: Origin ekleyin:
const allowed = [
'https://app.example.com',
'http://localhost:5173'
];
if (allowed.includes(req.headers.origin)) {
res.set('Access-Control-Allow-Origin', req.headers.origin);
res.set('Vary', 'Origin');
}
Vary: Origin, önbelleklerin veya CDN'lerin bir kaynak için üretilen başlığı başka bir kaynağa sunmasını önler.
5. İstenen başlık veya yöntem izin verilmiyor
Örnek hata mesajları:
-
authorizationbaşlığı,Access-Control-Allow-Headerstarafından izin verilmiyor. -
PUTyöntemi,Access-Control-Allow-Methodstarafından izin verilmiyor.
Preflight yanıtı geliyor, ancak frontend'in ihtiyaçlarını karşılamıyordur. Yanıtı kullandığınız tüm yöntem ve başlıkları içerecek şekilde genişletin:
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-Id
Başlık adları büyük/küçük harfe duyarlı değildir. HTTP yöntemlerini ise büyük harfle yazın.
6. Preflight isteği için yönlendirmeye izin verilmiyor
Preflight isteği 301 veya 302 döndüren bir URL'ye ulaştığında tarayıcı yönlendirmeyi takip etmeyebilir.
Yaygın nedenler:
-
httpadresininhttpsadresine yönlendirilmesi - Eksik sondaki eğik çizginin framework tarafından eklenmesi
-
/v1/ordersadresinin/v1/orders/adresine yönlendirilmesi
Frontend'i doğrudan nihai URL'ye yönlendirin. HTTPS'i baştan kullanın, yönlendiricinin sondaki eğik çizgi kuralına uyun ve OPTIONS isteğinin 3xx yerine 2xx döndürdüğünü manuel olarak doğrulayın.
Sunucu yapılandırma örnekleri
Express
Başlıkları elle yazmak yerine resmi cors ara yazılımını kullanın:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({
origin: ['https://app.example.com', 'http://localhost:5173'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Authorization', 'Content-Type'],
credentials: true,
maxAge: 86400
}));
CORS'u kimlik doğrulama ara yazılımından önce bağlayın. Böylece preflight istekleri eksik belirteç nedeniyle reddedilmez.
Python kullanıyorsanız, aynı başlık mantığını Flask-CORS uzantısı ile uygulayabilirsiniz.
Spring Boot
WebMvcConfigurer üzerinden küresel yapılandırma:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/v1/**")
.allowedOrigins("https://app.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type")
.allowCredentials(true)
.maxAge(86400);
}
}
Spring Security kullanıyorsanız güvenlik filtre zincirinde de CORS'u etkinleştirin:
.cors(Customizer.withDefaults())
Aksi halde güvenlik katmanı, MVC yapılandırmanız devreye girmeden önce preflight isteklerini engelleyebilir. Ayrıntılar için Spring CORS belgelerine bakın.
Nginx
Nginx istekleri uygulamanızın önünde sonlandırıyorsa preflight yanıtını kenarda verebilirsiniz:
location /v1/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
add_header Access-Control-Max-Age 86400 always;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Vary "Origin" always;
proxy_pass http://backend;
}
always bayrağı önemlidir. Bu bayrak olmadan Nginx, 4xx ve 5xx yanıtlarında add_header yönergelerini uygulamaz.
CORS başlıklarını hangi katmanın yöneteceğine de karar verin. Hem Nginx hem de uygulama başlık eklerse yinelenen değerler, örneğin Access-Control-Allow-Origin: *, *, tarayıcının yanıtı reddetmesine neden olabilir.
Apidog ile tarayıcı dışında CORS hata ayıklama
Konsol hatası yalnızca tarayıcının yanıtı engellediğini söyler; sunucunun gerçekte ne gönderdiğini göstermez.
Apidog bir masaüstü API istemcisi olduğu için istekleri tarayıcının CORS kontrollerine tabi değildir. Frontend'in gönderdiği isteği Apidog'da tekrar ederek sorunun API'de mi, CORS yapılandırmasında mı olduğunu ayırabilirsiniz.
- Apidog'da istek başarılıysa API mantığı büyük olasılıkla doğrudur; sorun CORS başlıklarındadır.
- Apidog'da da başarısızsa CORS görünümüne bürünmüş normal bir API hatasıyla karşı karşıyasınız.
Uygulanabilir hata ayıklama akışı
Gerçek isteği tekrar oynatın.
Tarayıcının Network sekmesindeki başarısız isteği Apidog'da aynı yöntem, başlıklar ve gövdeyle oluşturun. Durum kodunu ve yanıt gövdesini kontrol edin.Preflight isteğini elle test edin.
Yeni birOPTIONSisteği oluşturun ve şu başlıkları ekleyin:
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
- Yanıt başlıklarını inceleyin. Şu başlıkları frontend'in ihtiyaçlarıyla karşılaştırın:
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Eksik başlık, yanlış kaynak veya 3xx durum kodu burada hemen görünür.
-
Düzeltmeyi doğrulayın.
Sunucu yapılandırmasını değiştirdikten sonra aynı kayıtlı
OPTIONSisteğini yeniden gönderin ve başlıkların güncellendiğini kontrol edin.
Bu iş akışı, “API istemcimde çalışıyor ama tarayıcıda başarısız oluyor” sorununu hızlıca izole eder. İstemci CORS'u atladığı için başarılı olabilir; tarayıcı ise sunucudan gerekli başlıkları alamadığı için yanıtı engeller. Apidog'u ücretsiz indirin ve düzenli endpoint testlerinizin yanına bir OPTIONS isteği kaydedin.
30 saniyelik CORS kontrol listesi
Hatayı araştırmadan önce şunları kontrol edin:
- Başarısız yanıt
Access-Control-Allow-Originiçeriyor mu? - Değer sayfanızın kaynağıyla tam olarak eşleşiyor mu? Şema, ana bilgisayar ve port dahil; gereksiz sondaki eğik çizgi olmadan.
- Çerez veya kimlik doğrulama kullanıyor musunuz? Belirli bir kaynak ve
Access-Control-Allow-Credentials: truekullanın;*kullanmayın. -
OPTIONSisteği, gerekli yöntem ve başlıklarla birlikte2xxdöndürüyor mu? - Preflight URL'sinde herhangi bir yönlendirme var mı?
-
401,403ve500gibi hata yanıtları da başarılı yanıtlarla aynı CORS başlıklarını taşıyor mu?
On vakadan dokuzunda sorun bu maddelerden biridir. Apidog'da manuel bir OPTIONS isteğiyle doğrulayın, sunucu yapılandırmasını düzeltin ve geliştirmeye devam edin.
SSS
Neden yalnızca tarayıcıda CORS hatası alıyorum?
Çünkü CORS'u yalnızca tarayıcılar uygular. Aynı kaynak politikası, kötü amaçlı sayfaların kimliği doğrulanmış verileri okumasını engeller. curl, backend servisleri ve masaüstü istemcilerinde böyle bir kural yoktur.
İstek tarayıcı dışında başarılı oluyorsa API sağlıklı olabilir; sunucunuzun CORS başlıkları eksik veya yanlış yapılandırılmıştır.
CORS Postman veya Apidog için geçerli mi?
Hayır. Postman ve Apidog, tarayıcı sanal alanında çalışan web sayfaları değil, masaüstü uygulamalarıdır. Bu nedenle CORS'u atlarlar ve sunucunun ham yanıt başlıklarını gösterirler.
Bu araçlar CORS hata ayıklamada kullanışlıdır; ancak masaüstü istemcisinde başarılı bir istek, tarayıcı davranışı hakkında tek başına kanıt değildir. Yalnızca başarısız olan katmanı izole eder.
Daha fazla bilgi için API test teknikleri rehberine ve Postman CORS testi rehberine bakın.
CORS hatası bir güvenlik özelliği mi, yoksa hata mı?
Bir güvenlik özelliğidir. Sunucu açıkça izin vermediği sürece tarayıcı, kaynaklar arası yanıt verilerini script'lere sunmaz.
Tarayıcıda CORS'u bayraklarla veya uzantılarla devre dışı bırakmak yalnızca sorunu kendi makinenizde gizler. Diğer kullanıcılar yine aynı hatayı görür. Kalıcı çözüm sunucu başlıklarını düzeltmektir.
Access-Control-Allow-Origin: * her yerde kullanılabilir mi?
Yalnızca çerez veya kimlik doğrulama içermeyen herkese açık, salt okunur API'lerde kullanın.
Joker karakter kimlik bilgileriyle birlikte reddedilir ve verilerinizi her kaynağa açar. Kimlik doğrulamalı API'lerde:
- İzin verilen kaynaklardan oluşan bir liste tutun.
- Eşleşen kaynağı yanıtlayın.
- Paylaşılan önbelleklerin yanıtları ayırması için
Vary: Origingönderin.
Top comments (0)