İçindekiler
- 1 Ofiste Küçük Bir Paniğin Hikayesi: Tarayıcı “Nazı”, Çerez Bayrakları ve Bir Kahve
- 2 Bu Bayraklar Neyi Kurtarıyor? Biraz İç Yüzünü Anlatalım
- 3 SameSite=Lax mı Strict mi? Peki ya None Ne Zaman?
- 4 Nginx’te Çerez Bayraklarını Eklemek: Proxy Arkasında Zarif Dokunuş
- 5 Apache’de Çerez Bayrakları: mod_headers ile “Son Dokunuş”
- 6 Uygulama Kodunda Doğru Ayar: Node, PHP, Laravel, Django, Spring
- 7 Proxy/CDN Arkasında Güvenli Akış: Küçük Bir Yol Haritası
- 8 Ödeme, SSO ve İframe Dönüşleri: “Neden Oturum Kayboldu?” Sorunsalını Yumuşatmak
- 9 Doğru Bayrak + Doğru Ömür + Doğru Kapsam: Küçük Ayarların Büyük Farkı
- 10 Set-Cookie Nasıl Görünmeli? Gözümüz Alışsın
- 11 Test, Doğrulama ve Küçük Araçlar
- 12 Let’s Encrypt, Sertifikalar ve Secure Bayrağının “Hakkını Vermek”
- 13 Küçük Tuzaklar: HttpOnly Her Zaman Mı? Ve “Görünmez” Bozulmalar
- 14 Adım Adım Uygulama: Basit Bir Yol Haritası
- 15 Kapanış: Küçük Bayraklar, Büyük Sükunet
Ofiste Küçük Bir Paniğin Hikayesi: Tarayıcı “Nazı”, Çerez Bayrakları ve Bir Kahve
Hiç beklemediğin bir anda, giriş formu düzgün çalışmasına rağmen kullanıcıların “hesaba giremiyorum” diye yazdığını gördün mü? Ben bir kere değil, birkaç kere yaşadım. Hatta birinde kart ödemesi sayfasından döndüğümüzde oturum “yok olmuş” gibi davranıyordu. Ekipte herkes doğru parolayı girdiğine yemin ediyordu, loglarda hata görünmüyordu, ama tarayıcı sessiz bir şekilde oturum çerezini reddediyordu. İşte o gün, masadaki kahvemi bırakıp şu küçük ama kritik başlıklara tekrar baktım: SameSite, Secure ve HttpOnly.
Bugün tam da bu konuyu, biraz sohbet havasında ve bolca gerçek örnekle konuşalım istiyorum. Neyi, nerede, niçin işaretlediğimizi bilince, hem güvenliği artırıyoruz hem de o garip “payment dönüşünde çıkış yapmışsın gibi” hissini çözüyoruz. Yazının devamında önce bu bayrakların ne işe yaradığını netleştireceğiz. Sonra Nginx ve Apache tarafında nasıl eklenir, uygulama kodunda nasıl doğru ayarlanır, proxy/CDN arada iken neleri unutmamak gerekir, hepsini adım adım ele alacağız. En sonda da pratik test yöntemleri ve küçük tüyolarla tamamlarız.
Bu Bayraklar Neyi Kurtarıyor? Biraz İç Yüzünü Anlatalım
Çerez, tarayıcının elindeki küçük bir not gibi. Sunucu “bu kullanıcı Ahmet” der, tarayıcı da onu bir çerezle hatırlar. Ama bu notu herkes görebilsin mi, her durumda yollansın mı, güvenli olmayan bağlantıda bile gitsin mi? Cevapları netleştirmek için üç arkadaş var: SameSite, Secure ve HttpOnly. Her biri, tarayıcının “bu çerez ne zaman, nasıl gönderilsin” kararını şekillendiriyor.
Secure, çerezin sadece HTTPS ile taşınmasını sağlar. Yani “şifrelenmemiş yoldan geçirme” demenin kısa yolu. HttpOnly, JavaScript’in çereze dokunmasını engeller; XSS gibi bir senaryoda saldırgan okuyamasın diye. SameSite ise çerezin site dışından gelen isteklerle gönderilmesini bir ölçüde kısıtlar. Böylece CSRF gibi “başka siteden gelen istek, kullanıcının oturumuyla iş yaptırsın” senaryolarında fren görevi görür. Basit görünüyor ama ayarını doğru yapmak, özellikle yönlendirmeler ve ödeme/SSO dönüşleri olan projelerde, ince bir iş.
SameSite=Lax mı Strict mi? Peki ya None Ne Zaman?
Mesela şöyle düşünün: Sitenize bir sosyal ağdan link tıklanarak gelindi. Tarayıcı, “başka siteden gelindiğine” bakar. Eğer çereziniz SameSite=Strict ise, bu ziyarette oturum çerezi gönderilmeyebilir ve kullanıcı sanki çıkış yapmış gibidir. Bu, bazı projelerde istenen davranıştır çünkü en katı güvenliği sağlar. Ama çoğu müşteri yolculuğu ilk ziyareti dışarıdan, bir linkten başlatır. O yüzden Strict, bazen fazla katı gelebilir.
SameSite=Lax, günlük hayatta daha yumuşak davranır. Dış linkten geçişlerde çerezi çoğu normal gezintide korur, ancak “arka planda” yapılan bazı isteklerde tutucu olabilir. Birçoğumuzun giriş formu ve normal sayfa gezinme akışında Lax gayet tatlı çalışır. Eğer projenin “başka domain içinde iframe ile çalışan widget” gibi bir ihtiyacı yoksa, Lax genelde hem güvenlik hem kullanım açısından dengeli noktadır.
Bir de SameSite=None var. Bu, her türlü site dışı senaryoda çerezin gönderilmesine izin verir ama bir şartı beraberinde getirir: Secure zorunludur. Yani None kullanıyorsanız HTTPS dışı yollar kapalı olmalı. Bu ayar genelde üçüncü taraf entegrasyonları, SSO ve ödeme servislerinde iframe veya domainler arası akış gerekiyorsa anlamlı olur. Kısaca özetleyeyim: çoğu uygulama için Lax iyi bir başlangıçtır; işe yaramazsa kontrollü bir şekilde None’a geçilir; Strict ise çok kapalı politikalarda değerli bir güvenlik kilidi sunar.
Nginx’te Çerez Bayraklarını Eklemek: Proxy Arkasında Zarif Dokunuş
Uygulamanız Nginx’in arkasında çalışıyorsa, bazen upstream uygulama çerezleri flagsiz bırakır ve siz Nginx’te “son anda” düzeltmek istersiniz. Burada iki yol kullanıyorum. Eğer Nginx sürümünüz destekliyorsa proxy_cookie_flags ile “şu isimli çerezlere Secure, HttpOnly ve SameSite ekle” diyebilirsiniz. Daha eski veya farklı senaryolarda ise proxy_cookie_path hilesiyle Set-Cookie satırına ek nitelikler koymak mümkün.
İlk olarak, isme göre bayrak eklemek pratik olur. Örneğin oturum çerezi JSESSIONID veya sessionid ise aşağıdakine benzer:
location / {
proxy_pass http://app;
# Belirli bir çerez adı için bayraklar
proxy_cookie_flags sessionid Secure HttpOnly SameSite=Lax;
# Ad desenini de kullanabilirsiniz
proxy_cookie_flags ~*session Secure HttpOnly SameSite=Lax;
}
Eğer bu direktif sizde yoksa, şu yöntemi deneyebilirsiniz. Upstream Set-Cookie geldiğinde path’e dokunur gibi görünen ama aslında bayrak ekleyen bir düzenleme:
location / {
proxy_pass http://app;
proxy_cookie_path / "/; Secure; HttpOnly; SameSite=Lax";
}
Burada dikkat edilecek nokta, bu yöntemin tüm çerezlere dokunmasıdır. Bazı çerezlerin HttpOnly olmaması gerekebilir (örneğin istemci tarafında JS’in okuması gereken bir tercih çerezi). Bu durumda sadece oturum çerezi gibi kritik olanları hedefleyen yöntemi tercih etmek daha sağlıklı. Ayrıca, Nginx TLS ayarlarınızı güçlendirmek, Secure bayrağının hakkını verir; bu konuda Nginx/Apache’de ECDSA + RSA ikili SSL kullanımı ile uyumluluk ve hız arasında güzel bir denge kurulabilir.
Apache’de Çerez Bayrakları: mod_headers ile “Son Dokunuş”
Apache tarafında, mod_headers ile Set-Cookie başlığını düzenlemek günlük işim haline geldi. İki tarz var: ya tüm Set-Cookie satırlarına ekleme yaparsınız, ya da belirli bir çerez adına hedefli düzenleme koyarsınız. Genel ekleme yaparsanız istenmeyen çerezler de HttpOnly olup JS’ten kaybolabilir, buna dikkat.
Genel ekleme örneği şöyle:
<IfModule mod_headers.c>
Header edit Set-Cookie ^(.*)$ "$1; HttpOnly; Secure; SameSite=Lax"
</IfModule>
Daha hedefli bir yaklaşım için, mesela JSESSIONID veya session gibi belirli bir çerezi yakalayıp bayrak eklemek hoş olur:
<IfModule mod_headers.c>
Header edit Set-Cookie "^(JSESSIONID=.*)$" "$1; HttpOnly; Secure; SameSite=Lax"
</IfModule>
Apache yapılandırırken şunu da not edin: HTTPS arkasında olduğunuzdan emin olun. Load balancer önünde TLS bitiyor ve Apache’ye HTTP geliyorsa, Secure bayrağı işini yapar ama uygulama “bu istek güvenli mi” algısını doğru kuramaz. X-Forwarded-Proto gibi başlıkların doğru taşındığından ve uygulamanın proxy’ye güvendiğinden emin olmak gerekiyor.
Uygulama Kodunda Doğru Ayar: Node, PHP, Laravel, Django, Spring
En sağlam yol, çerezi uygulamanın kendisinin doğru bayraklarla set etmesi. Bu hem niyetini net ifade eder hem de proxy katmanına bağımlılığı azaltır. Aşağıda pratik örneklerle birkaç dil ve çerçeve paylaşıyorum. Mesela Express’te oturum çerezi için ayarlar çok okunaklı:
// Express (Node.js)
app.set('trust proxy', 1); // LB/CDN arkasında doğru güven algısı için
app.use(session({
secret: 'bir-nihai-sir',
name: 'sessionid',
cookie: {
httpOnly: true,
secure: true, // üretimde HTTPS şart
sameSite: 'lax', // 'strict' veya 'none' ihtiyaca göre
path: '/',
}
}));
PHP’de modern imzalı parametrelerle setcookie gayet okunaklı hale geldi. Küçük ama etkili bir örnek:
// PHP (7.3+)
setcookie(
'sessionid',
$value,
[
'expires' => time() + 3600,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]
);
Laravel’de iş daha da güzel, çünkü yapılandırma dosyalarıyla düzeni tutarlı kılabiliyorsun. config/session.php içinde şunları seviyorum:
// Laravel (config/session.php)
'require_https' => true,
'http_only' => true,
'same_site' => 'lax', // 'strict' / 'none' seçenekleri akışa göre
'secure' => env('SESSION_SECURE_COOKIE', true),
Django tarafında oturum çerezi ve CSRF çerezi ayrı düşünülmeli. Oturum çerezi HttpOnly olmalı ama CSRF çerezi çoğu projede JS ile okunur. O yüzden CSRF çerezine HttpOnly vermek projene göre kırıcı olabilir. Güzel bir başlangıç:
# Django settings.py
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = True
# CSRF_COOKIE_HTTPONLY = False # Varsayılan çoğu projede bırakılır; ihtiyaca göre değerlendirin
Spring Boot’ta ayarlar da sadeleşti. Oturum çerezi için doğrudan yapılandırma yeterli:
# Spring Boot (application.properties veya yml)
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.same-site=lax
Burada ortak payda şu: üretim ortamında HTTPS zorunlu. Aksi halde Secure bayrağı olan çerez hiç gitmez ve “bazen çalışıyor bazen değil” gibi can sıkıcı senaryolar yaşanabilir. Ayrıca proxy arkasında çalışıyorsanız uygulamanın “güvenli bağlantı algısını” X-Forwarded-Proto, X-Forwarded-For vb. başlıklara göre doğru kurmasını sağlayın.
Proxy/CDN Arkasında Güvenli Akış: Küçük Bir Yol Haritası
Birçok projede TLS, CDN veya load balancer katmanında sonlanıyor. Uygulama ile reverse proxy arasında HTTP konuşulsa bile tarayıcıyla olan hat güvenli olmalı. Bu noktada iki şeye dikkat ediyorum. Birincisi, uygulamada “trust proxy” veya eşdeğeri ayarı yaparak gelen isteğin aslında HTTPS üzerinden geldiğini anlayabilmek. İkincisi, Secure işaretli çerezin arada yanlışlıkla düşmemesi.
HAProxy, Nginx veya bulut sağlayıcının CDN’i devredeyse, doğru başlıkların ayarlandığına emin olun. Örneğin X-Forwarded-Proto=https değerini set etmek ve uygulamada buna güvenmek, “güvenli mi” algısını netleştirir. Bu konularla ilgileniyorsanız, katman 7 akışlarını sade sade anlattığım HAProxy ile L4/L7 yük dengeleme yazısına göz atmak hoş bir tamamlayıcı olabilir.
Bazı servisler çerezi kendileri set eder ve siz proxy’de sadece dokunmak istersiniz. Bu durumda Nginx’te belirli çerezleri hedefleyen proxy_cookie_flags yaklaşımı, Apache’de mod_headers ile isim bazlı edit en risksiz olanıdır. Tüm çerezleri toptan düzenlemek, ileride “şu küçük tercih çerezi JS’te görünmüyor” sürprizi doğurabilir.
Ödeme, SSO ve İframe Dönüşleri: “Neden Oturum Kayboldu?” Sorunsalını Yumuşatmak
En çok sorun çıkaran yerler, kullanıcıyı dış siteye gönderip geri aldığımız akışlar. Ödeme sayfası, SSO sağlayıcısı veya üçüncü taraf widget bu kategoride. Eğer oturum çerezi Strict ise, dönüşte tarayıcı bu çerezi göndermeyebilir ve “çıkış yapmışsın” gibi görünür. Lax çoğu zaman bu akışa daha anlayışlıdır ama bazı özel POST dönüşlerinde yine ketum davranabilir.
Bu durumda iki adımlı bir çözümü seviyorum. İlk olarak, gerçekten gerekiyorsa belirli akışlar için SameSite=None kullanmak ve bu çereze mutlaka Secure eklemek. İkinci olarak, server tarafında CSRF korumasını doğru kurmak; çünkü None dediğinizde çapraz site istekleri de çerezi taşıyabilir. None her derde deva değil ama “zorunlu” entegrasyonlarda tek doğru tercih olabiliyor. Burada uygulamanın oturum süresini, token yenileme akışını ve yönlendirme logiğini beraber düşünmek gerekiyor.
E-ticaret tarafında bu ayarlar bir de uyumluluk ve denetim dünyasına bağlanır. Kredi kartı verisi uygulamaya girmese bile, oturum yönetimi ve güvenli iletim konuları önemlidir. Bu çizgiyi daha geniş çerçevede konuşmak istersen, e‑ticarette PCI DSS ile uyumlu kalma üzerine notlarımı faydalı bulabilirsin.
Doğru Bayrak + Doğru Ömür + Doğru Kapsam: Küçük Ayarların Büyük Farkı
Bayraklar tek başına yetmez; Domain, Path ve çerezin ömrü de davranışı değiştirir. Sitenin sadece belirli bir bölümünde geçerli olması gerekiyorsa Path yardımıyla alanı daraltmak iyi bir fikir. Subdomain’ler arasında paylaşım gerekliyse Domain’i dikkatle ayarlayın ve gereksiz geniş bırakmayın. “Kök domain herkese açık olsun” yaklaşımı bazen güvenlik riskini büyütür.
Oturum çerezini genelde tarayıcı oturumu boyunca yaşatmak, kalıcı çerezi ise makul bir tarihte bitirmek iyi bir dengedir. Çok uzun ömür, kayıp cihaz veya paylaşılan bilgisayarda risk demek olabilir. Çok kısa ömür ise kullanıcıyı yorar. Burada ekipçe karar alıp UI akışıyla senkron gitmek en güzeli.
Set-Cookie Nasıl Görünmeli? Gözümüz Alışsın
Tarayıcı geliştirici araçlarında Network sekmesini açıp yanıt başlıklarına bakmak, en hızlı teşhis yolu. İdeal bir oturum çerezi şöyle görünür:
Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure; SameSite=Lax; Expires=Tue, 12 Nov 2026 10:00:00 GMT
Eğer bir entegrasyon için None gerekiyorsa:
Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure; SameSite=None
Gözle teyit ettikten sonra, uygulama ve proxy katmanında yazdığınız kuralların doğru çerezlere değdiğini anında anlarsınız. Gerekirse kısa süreliğine staging’de logları artırıp “Set-Cookie” başlıklarını kayıt altına alın. Böylece prod’a geçmeden sürprizleri yakalarsınız.
Test, Doğrulama ve Küçük Araçlar
Tarayıcı tarafında Chrome/Firefox geliştirici araçları ilk durak. Uygun bayraklar görünüyor mu, istekler cross-site olduğunda çerez gidiyor mu, hepsini pratikte test edebilirsiniz. Ayrıca resmi belgeler hem sentaks hem davranış örnekleri açısından net bir referans sağlar. Ben çoğu zaman ayrıntı için MDN’in Set-Cookie sayfasına bakıyorum; SameSite davranışlarını özetleyen bölümü de tahmin yürütmek yerine hızlıca doğrulamayı sağlıyor.
Güvenli oturum yönetimi baştan sona bir yolculuk. Çerez bayrakları bunun önemli parçası ama tek başına çözüm değil. Parola sıfırlama, oturum yenileme, sabit sürede yeniden doğrulama gibi süreçler bir arada ele alındığında güvenlik hissedilir şekilde yükseliyor. Göz atmak istersen, OWASP’in oturum yönetimi notları pratik bir kontrol listesi gibi iş görür.
Let’s Encrypt, Sertifikalar ve Secure Bayrağının “Hakkını Vermek”
Secure bayrağı, HTTPS olmadan anlamını yitirir. Bu yüzden sertifika tarafını sağlam kurmak en baştaki görevlerden. Çok alan adı barındırıyorsan, Let’s Encrypt ile otomasyonu akıllıca kurgulamak hem işini kolaylaştırır hem de kesintisiz güvenli hat sağlar. Bu noktada yaşadığım tecrübeleri anlattığım Let’s Encrypt ile çok alan adında SSL alma stratejileri yazısı, Secure bayrağıyla doğal bir bütünlük kuruyor.
Bir de sertifika türü ve uyumluluk konusu var. ECDSA ile daha çevik el sıkışmalar, RSA ile geniş uyumluluk arıyorsan ikilisini aynı anda kullanmak cidden hoş bir denge sunuyor. Bunu Nginx/Apache’de nasıl yaptığımı ayrıntılarıyla paylaştım; Secure çerezler için güçlü TLS, belki de atılan en değerli adım.
Küçük Tuzaklar: HttpOnly Her Zaman Mı? Ve “Görünmez” Bozulmalar
HttpOnly kuralını çok seviyorum ama her çereze körlemesine uygulamıyorum. Mesela CSRF tokenini cookie ile paylaşan bazı yapılarda JS’in onu okuyup header’a koyması gerekir. Böyle bir çereze HttpOnly verirsen, token’ı okuyamaz ve formlar çalışmaz. Bu yüzden çerezleri iki kategoriye ayır: güvenlik açısından kritik olan oturum/kimlik çerezleri ve istemcinin bilerek okuması gereken işlevsel çerezler.
Bir başka tuzak da Domain/Path oyunu. Üst alan adında set edilen çerez alt alanlarda beklenmedik etki yapabilir. Ayrıca staging/preview alan adlarında güvenli olan bir davranış prod ortamda cross-site kabul edilebilir. Testleri prod benzeri domainde yapmak, gerçekçi sonuç verir. Son olarak, tarayıcıların zamanla varsayılanlarını değiştirdiğini unutma; bugün Lax varsayılan olabilir, ama yarın yeni güvenlik katmanları eklenebilir. Kodda niyeti açıkça ifade etmek bu yüzden güzel bir alışkanlık.
Adım Adım Uygulama: Basit Bir Yol Haritası
İşe en kolayından başlayalım. Önce geliştirici araçlarında çerezlerin mevcut haline bak. Hangi çerezler var, hangileri oturumla ilgili, hangileri işlevsel? Sonra bu çerezlerin her biri için olması gereken bayrakları belirle. Genelde oturum çerezine HttpOnly + Secure + Lax iyi bir üçlü olur. Ödeme/SSO gibi özel akış varsa o belirli çereze None verip Secure şartını es geçme.
Uygulama koduna bu niyeti yaz. Framework ayarları varsa onları kullan, yoksa setcookie veya eşdeğerini direkt çağır. Proxy katmanında, yalnızca gerekiyorsa düzenleme yap ve belirli isimleri hedefle. Son olarak, gerçek kullanıcı akışlarını test et: dış linkten gelen giriş, ödeme dönüşü, iframe içi widget, tek sayfa uygulaması istekleri. Bozulmalar genelde burada yakalanır.
Kapanış: Küçük Bayraklar, Büyük Sükunet
Çerez bayrakları ilk bakışta ufak bir ayrıntı gibi duruyor ama sistemi sakinleştiren birer güvenlik kemeri gibiler. Doğru ayarlandığında CSRF ihtimalini azaltır, XSS hasarını sınırlamaya yardımcı olur ve kullanıcıların garip “oturum kaybı” anlarını yatıştırır. Nginx/Apache’de son dokunuşlarla işinizi görürsünüz ama en sağlıklısı uygulamanın çerezi doğrudan doğruya doğru bayraklarla set etmesi.
Pratik bir tavsiye: önce üretime yakın bir ortamda gerçek akışları test edin, geliştirici araçlarında Set-Cookie’yi gözünüzle görün, sonra proxy kurallarını ekleyin. TLS tarafını da ihmal etmeyin; ikili sertifika stratejileri ve Let’s Encrypt ipuçları ile Secure bayrağı hak ettiği zemini bulur. Yolun sonundaki his şu: kurcaladığın o üç kelime, sitede fark edilmez bir huzur yaratır.
Umarım bu yazı işini kolaylaştırmıştır. Soruların varsa not al, aklına takılan akışları birlikte parçalarız. Bir dahaki yazıda yine gerçek örneklerle buluşalım; belki de sırada ters proxy ile cache kontrolünde hayat kurtaran küçük sihirler var.
