Oturum ve JWT'nin doğru kullanımı
- Oturum kimliğini ve JWT imzasını doğru üretip doğrulamayı, çerezi sertleştirmeyi, oturum sabitlemeyi önlemeyi ve token ömrünü ayarlamayı öğrenirsin.
- Bu derste bir JWT'yi komut satırında çözer, PyJWT ile doğru doğrular, çerezi __Host- önekiyle sertleştirir ve oturum sürelerini OWASP değerleriyle ayarlarsın.
Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +55 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.
HTTP durumsuz bir protokoldür. Sunucu, art arda gelen iki isteğin aynı kişiden geldiğini kendiliğinden bilmez. Oturum yönetimi bu boşluğu doldurur.
Kullanıcı bir kez giriş yapar, sonraki her istekte bir kimlik kanıtı taşır. O kanıt çalınırsa parola hiç sızmadan hesap el değiştirir. Bu yüzden oturum kimliği de parola kadar değerli bir sırdır. Bu derste Altay Kargo'nun müşteri paneli ve iade API'si üzerinden ilerlersin.
Önce iki modeli karşılaştırır, sonra bir token'ı açar ve imzayı kodda doğrularsın. Ardından çerezi sertleştirir, oturum ömrünü ayarlar ve token'ın tarayıcıda nerede duracağına karar verirsin.
İki model: sunucu oturumu ve JWT
Sunucu tarafı oturumda sunucu, giriş anında rastgele bir oturum kimliği üretir ve onu bir çerezle tarayıcıya yollar. Kullanıcının kim olduğu ve yetkileri sunucudaki oturum deposunda durur. Çerez yalnız bir işaretçidir. Kimliğin tek görevi tahmin edilemez olmaktır.
OWASP en az 64 bit entropi ister. Bu rastgelelik kriptografik rastgele sayı üretecinden (CSPRNG) gelir. Artan sayı, zaman damgası ya da kullanıcı adının özeti kimlik olamaz. Python'da secrets.token_urlsafe(32), 256 bit rastgelelikten 43 karakterlik bir kimlik üretir.
Çoğu çatının oturum yöneticisi bu işi zaten doğru yapar; kendi üretecini yazma. Kimliği adres çubuğuna da koyma. Adres, tarayıcı geçmişine, sunucu günlüklerine ve Referer başlığına düşer.
JWT (JSON Web Token) modelinde sunucu, oturum bilgisini token'ın içine yazar ve imzalar. Her istekte veritabanına bakmaz; imzayı doğrular ve içeriğe güvenir. Durum tutmadığı için yatay ölçeklenmesi kolaydır, mikroservislerde bu yüzden yaygındır. Bedeli de vardır: yayımlanmış bir token'ı süresi dolmadan tek tek iptal etmek zorlaşır.
JWT üç parçadan kurulur. Header algoritma ve tür bilgisini, payload iddiaları (claims) taşır.
İmza (signature) bu iki parçanın değişmediğini kanıtlar. Parçalar Base64URL ile kodlanır ve noktayla birleşir. Kodlama şifreleme değildir; token'ı gören herkes payload'ı okur.
$ TOKEN='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJrLTEwNDIiLCJyb2wiOiJrdXJ5ZSIsImF1ZCI6ImlhZGUtYXBpIiwiaXNzIjoiaHR0cHM6Ly9raW1saWsuYWx0YXlrYXJnby5leGFtcGxlIiwiaWF0IjoxNzkwMDAwMDAwLCJleHAiOjE3OTAwMDA5MDB9.QLW2E_lzB8ATEAVHIfxQs1itTNIJEA1xi5dkKYHpzpU'
$ echo "$TOKEN" | cut -d. -f1 | base64 -d
{"alg":"HS256","typ":"JWT"}
$ echo "$TOKEN" | cut -d. -f2 | base64 -d
{"sub":"k-1042","rol":"kurye","aud":"iade-api","iss":"https://kimlik.altaykargo.example","iat":1790000000,"exp":1790000900}Komutları sırayla oku. cut -d. -f1 token'ı noktalardan böler ve ilk parçayı, yani header'ı alır. base64 -d kodlamayı açar: algoritma HS256, tür JWT.
İkinci komut payload'ı açar. sub kullanıcıyı, rol yetkiyi, aud token'ın hangi servis için üretildiğini söyler. iss, token'ı üreten kimlik sunucusudur.
iat üretim, exp son kullanma zamanıdır. İkisi de Unix zamanıdır ve aradaki fark 900 saniye, yani 15 dakikadır.
Doğrulama yalnız imzaya değil, bu alanlara da bakar. Bu örnekte base64 sorunsuz çalıştı, çünkü parça uzunlukları dördün katıydı. Base64URL dolgu karakterini (=) atar, + ve / yerine - ve _ kullanır. Uzunluk tutmazsa sona = eklersin, - ve _ karakterlerini de tr ile geri çevirirsin.
Payload'ı herkes okuyabildiği için içine parola, API anahtarı ya da gereksiz kişisel veri yazma. T.C. kimlik numarası ya da telefon, token'ın geçtiği her günlükte iz bırakır. İmza bütünlüğü korur, gizliliği korumaz.
Bir alışkanlık hatası daha var. Üretimden aldığın bir token'ı çözmek için üçüncü taraf bir web sitesine yapıştırma. O token hâlâ geçerli bir oturumdur ve artık başkasının sunucusundadır. Yukarıdaki gibi yerel komutlar ya da kendi aracın yeter.
Etkinlik 1 / 8
JWT'nin hangi parçası, içeriğin sunucu onayı olmadan değiştirilmediğini garanti eder?
Diyagramda doğru kutuyu tıkla.
Etkinlik 2 / 8
İade API'sinin günlüğüne bir token'ın payload parçası düşmüş. Parçayı çöz. rol iddiasının değeri ne?
Araçları sırayla dene. Yanlış araç bozuk çıktı verir.
İmzayı doğru doğrula
Doğrulamadaki en ünlü hata, algoritma seçimini token'a bırakmaktır. Bazı eski kütüphaneler header'daki alg alanını olduğu gibi kabul ediyordu.
Saldırgan alg değerini none yapıp imzayı boş bırakırsa, kütüphane imza denetimini hiç çalıştırmıyordu. Saldırgan da payload'daki rolü istediği gibi yazıyordu. Aynı kökten gelen ikinci hata, algoritma karışıklığıdır (algorithm confusion). Sunucu RS256 bekler ve imzayı genel (public) anahtarla doğrular.
Saldırgan token'ı HS256 ile imzalar, sır olarak da o genel anahtarı kullanır. Kütüphane alg alanına uyarsa genel anahtarı HMAC sırrı sanır ve sahte imzayı kabul eder. İki saldırının çaresi aynıdır. İzinli algoritmaları sunucu kendi tarafında sabit bir listeyle belirler, token'ın beyanına bakmaz.
import os
import jwt # PyJWT 2.x
ANAHTAR = os.environ["JWT_IMZA_ANAHTARI"] # en az 256 bit rastgele sır
def token_dogrula(token):
return jwt.decode(
token,
ANAHTAR,
algorithms=["HS256"], # izinli liste sunucuda
audience="iade-api", # başka servisin token'ı düşer
issuer="https://kimlik.altaykargo.example",
options={"require": ["exp", "iat", "sub"]}, # exp yoksa reddet
)Kodu satır satır oku. algorithms listesi kararın sunucuda verildiğini gösterir. alg:none ya da RS256 taşıyan token InvalidAlgorithmError ile düşer. PyJWT bu listeyi zorunlu tutar; vermezsen decode hiç çalışmaz.
audience, token'ın aud iddiasını iade-api ile karşılaştırır, tutmazsa InvalidAudienceError fırlatır. Bu denetim olmasa ödeme servisi için üretilmiş bir token iade servisinde de geçerdi. issuer, token'ı kimin ürettiğini doğrular. require satırı sessiz bir tuzağı kapatır.
PyJWT, exp iddiası olmayan token'ı varsayılan olarak kabul eder ve böyle bir token hiç ölmez. require listesi eksik iddiada MissingRequiredClaimError fırlatır. Süresi dolmuş token ise ExpiredSignatureError ile reddedilir.
Doğrulama kodunda üç hata daha sık görülür. Birincisi, çözmek ile doğrulamayı karıştırmaktır. Node.js'teki jsonwebtoken paketinde decode fonksiyonu imzaya hiç bakmaz; doğrulayan fonksiyon verify'dır. PyJWT'de de verify_signature seçeneği kapatılırsa imzasız token geçer.
İkincisi, header'daki jku ve jwk alanlarına güvenmektir. Bu alanlar bir anahtar adresi ya da gömülü bir anahtar taşır. Sunucu onlara uyarsa saldırgan kendi anahtarıyla imzalar.
Anahtarı her zaman kendi yapılandırmandan al. Üçüncüsü zayıf HMAC sırrıdır. Kısa bir sır, ele geçen tek bir token üzerinde çevrimdışı kaba kuvvetle bulunur.
openssl rand -base64 32 komutu 32 rastgele bayt, yani 256 bitlik bir sır üretir. Çok servisli sistemde ES256 ya da EdDSA gibi asimetrik bir algoritma seç. Böylece yalnız kimlik sunucusu imzalar, öbür servisler yalnız doğrular.
Derinleş:alg:none nereden çıktı
JWT, Mayıs 2015'te RFC 7519 ile standartlaştı. İmza kuralları JWS (RFC 7515), algoritma adları JWA (RFC 7518) belgesinden gelir.
JWA, none adında imzasız bir algoritma da tanımlar. Bu tür, bütünlüğün başka bir katmanda sağlandığı durumlar için düşünülmüştü. Aynı yıl bir güvenlik araştırmacısı, birçok JWT kütüphanesinde iki kritik hata yayımladı.
Kütüphaneler alg alanını token'dan okuyordu. none gelince imzayı atlıyor, HS256 gelince RS256 için verilmiş genel anahtarı HMAC sırrı gibi kullanıyordu. Node.js jsonwebtoken paketinin 4.2.2 öncesi sürümlerindeki algoritma karışıklığı sonradan CVE-2015-9235 numarasını aldı.
Kütüphaneler bunun üzerine doğrulamada algoritma listesi istemeye başladı. Şubat 2020'de yayımlanan RFC 8725, JWT için en iyi uygulamaları topladı. Belge algoritma doğrulamasını, anahtar gücünü, aud ve iss denetimini açıkça ister.
Derinleş:Psişik imzalar: sıfırı kabul eden doğrulayıcı
Nisan 2022'de yayımlanan CVE-2022-21449, doğrulayıcının kendisinin de yanılabileceğini gösterdi. Java'nın 15 ile 18 arasındaki sürümlerinde ECDSA doğrulaması, iki bileşeni de sıfır olan bir imzayı geçerli sayıyordu.
ECDSA imzası r ve s adında iki sayıdan oluşur. Standart, ikisinin de sıfırdan büyük olmasını ister; bu denetim eksikti. Sonuçta ES256 ile imzalanmış JWT kabul eden bir Java servisi, imza alanı sıfırlardan oluşan sahte bir token'ı geçerli sayabiliyordu. Aynı açık SAML, OIDC id_token ve WebAuthn gibi ECDSA kullanan başka akışları da etkiliyordu.
Açığın adı, Doctor Who dizisindeki psişik kâğıttan gelir. O boş kâğıt, bakan kişiye görmek istediği belgeyi gösterir. Ders şudur: algoritma listesi ve anahtar doğru olsa bile kütüphane ve çalışma zamanı güncel kalmalıdır. Kimlik doğrulama kodu, bağımlılık güncellemesinde ilk yamanacak yerlerden biridir.
Etkinlik 3 / 8
Sunucu, doğrulamada yalnız izin verilen algoritma listesini (HS256) kabul ediyor. Gelen token header'ında alg:none yazıyor. Doğrulama fonksiyonu ne yapar?
Önce kodu oku, sonra çıktıyı seç.
dogrula(token, gizliAnahtar, { izinliAlgoritmalar: ["HS256"] })Etkinlik 4 / 8
İade API'si için 256 bitlik yeni bir HMAC sırrı üret. Çıktı Base64 biçiminde olsun.
Çerezi sertleştir, kimliği yenile
Oturum çerezde taşınıyorsa çerezin öznitelikleri ilk savunma hattıdır. HttpOnly, sayfadaki JavaScript'in çerezi okumasını engeller. Bir XSS açığı çalışsa bile betik oturum değerini göremez.
Secure ile çerez yalnız HTTPS bağlantısında gider. SameSite, başka bir sitenin başlattığı isteklere çerezin eklenip eklenmeyeceğini belirler. Strict en sıkı değerdir; çerez yalnız aynı siteden gelen isteklerde gider.
Lax, başka siteden gelen bağlantı tıklamalarında çerezi gönderir, çapraz site form gönderimini (POST) ise keser. None her istekte gönderir ve Secure ister. Bazı tarayıcılar SameSite yazılmamışsa Lax varsayar. Bu varsayıma güvenme, değeri açıkça yaz.
HTTP/1.1 200 OK Set-Cookie: __Host-oturum=HCTE8cIVF4PJTkMBSsbl1Pu0t1yEyr1ePTWManGHEHo; Path=/; Secure; HttpOnly; SameSite=Lax
Satırı parça parça oku. __Host- öneki tarayıcıya üç kural dayatır. Çerez Secure taşır, Domain özniteliği taşımaz ve Path=/ olur.
Kurallardan biri bozulursa tarayıcı çerezi hiç kaydetmez. Domain olmadığı için çerez yalnız onu kuran ana makineye gider. Ele geçirilmiş bir alt alan adı, örneğin eski bir kampanya.altaykargo.example sitesi, bu çerezin üzerine yazamaz.
OWASP bu öneki oturum kimlikleri için önerir. Değer, 43 karakterlik rastgele kimliktir. Max-Age ya da Expires olmadığı için bu bir oturum çerezidir. Asıl süre sınırını ise sunucu uygular.
Etkinlik 5 / 8
Oturum çerezinin tarayıcı betiği tarafından okunmasını engelleyen özniteliği ekle.
İsteği tamamla ve gönder.
Oturum sabitleme (session fixation, CWE-384) şöyle işler. Saldırgan siteden geçerli ama girişsiz bir oturum kimliği alır. Bu kimliği bir yolla kurbanın tarayıcısına yerleştirir. Ele geçirilmiş bir alt alan adından çerez kurmak ya da kimliği adresten kabul eden bir uygulama buna örnektir.
Kurban giriş yapar. Uygulama aynı kimliği kullanmaya devam ederse, saldırganın bildiği kimlik artık oturum açmış bir kullanıcıya aittir. Çare basittir: girişte ve her yetki değişiminde eski oturumu sil, yeni kimlik üret. __Host- öneki yerleştirme yollarından birini kapatır, yenileme ise hepsini.
app.post("/giris", async (req, res, next) => {
const kullanici = await kimlikDogrula(req.body.eposta, req.body.parola);
if (!kullanici) return res.status(401).json({ hata: "E-posta ya da parola hatalı" });
// Eski kimliği at, yenisini üret: sabitlenmiş kimlik işe yaramaz
req.session.regenerate((err) => {
if (err) return next(err);
req.session.kullaniciId = kullanici.id;
res.json({ tamam: true });
});
});regenerate fonksiyonu eski oturumu depodan siler, yeni kimlik üretir ve yeni çerezi yanıta yazar. Kullanıcı bilgisi ancak ondan sonra oturuma girer. Testte, girişten önceki ve sonraki çerez değerlerinin farklı olduğunu denetle.
Çerezle taşınan oturumun bir yan etkisi daha var: tarayıcı çerezi isteğe kendiliğinden ekler. Siteler arası istek sahteciliği (CSRF) buradan doğar. CWE-352, 2025 CWE Top 25 listesinde 3. sıradadır.
SameSite tek başına yetmez. Lax, GET ile yapılan üst düzey gezinmede çerezi yine gönderir; durum değiştiren bir GET işlemi korunmaz. OWASP, durum değiştiren isteklerde anti-CSRF jetonu ya da Fetch Metadata başlığı Sec-Fetch-Site denetimi ister.
Etkinlik 6 / 8
Her önlemi, kapattığı riskle eşleştir.
Oturum ömrü, çıkış ve iptal
Çalınan bir oturumun zararı, oturumun ne kadar yaşadığına bağlıdır. OWASP iki ayrı süre ister. Boşta kalma süresi (idle timeout), kullanıcı istek yapmazsa oturumun kapanacağı süredir. Yüksek değerli uygulamada yaygın aralık 2-5 dakika, düşük riskli uygulamada 15-30 dakikadır.
Mutlak süre (absolute timeout) etkinlikten bağımsızdır. Bütün gün çalışan bir personel için 4-8 saat uygun bir aralıktır. İki süreyi de sunucuda uygula.
Çerezin Max-Age değeri tarayıcıya yalnız bir istektir; saldırgan çalınan çerezi istediği kadar saklar. Altay Kargo'nun müşteri paneli 30 dakika boşta kalma ve 8 saat mutlak süre kullanır. İade onaylayan yönetim paneli 5 dakikaya iner.
JWT'de sunucu kaydı olmadığı için süre, token'ın exp alanıdır. Token'ı süresi hiç dolmayan bir bilet gibi kullanmak yaygın bir hatadır. Access token'ı dakikalar düzeyinde tut; birçok ekip 5 ile 15 dakika arası seçer.
Uzun oturum için ayrı bir yenileme token'ı (refresh token) kullanırsın. Bu token HttpOnly çerezde durur, yalnız yenileme ucuna gider ve sunucuda iptal edilebilir bir kayıttır. Döndürme (rotation) kaydı güçlendirir.
Her yenilemede yeni bir yenileme token'ı verilir, eskisi geçersiz olur. Eski token ikinci kez gelirse iki taraftan biri çalıntı kullanıyordur. Sunucu hangisi olduğunu bilemez, o zincirdeki bütün token'ları iptal eder. RFC 9700, genel istemcide yenileme token'ının döndürülmesini ya da göndericiye bağlanmasını zorunlu tutar.
Çıkış (logout) da modele uymalıdır. Sunucu tarafı oturumda çıkış, oturum kaydını sunucudan siler; kimlik o anda geçersiz olur. İstemcide de çerezi boşaltırsın. Aynı adla, boş değerle ve Max-Age=0 ile yeni bir Set-Cookie yollarsın.
Saf JWT modelinde imzalı token, süresi dolana kadar teknik olarak geçerlidir. Çıkışta yenileme token'ı sunucuda iptal edilir, kısa ömür de kalan riski sınırlar. Hemen kesmen gerekirse token'ın benzersiz jti değerini kısa ömürlü bir kara listeye eklersin.
HTTP/1.1 204 No Content Set-Cookie: __Host-oturum=; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=0
Etkinlik 7 / 8
Altay Kargo'nun access token ömrü 30 gün olarak ayarlanmış. Token çalınırsa saldırgan bu süre boyunca geçerli kalır. En doğru düzeltme hangisidir?
Token'ı tarayıcıda nerede tutarsın
Tek sayfa uygulamalarında (SPA) token'ı localStorage'da tutmak yaygın bir tercihtir. Oysa localStorage sayfadaki her betiğe açıktır. Bir XSS açığı ya da ele geçirilmiş bir üçüncü taraf betik, token'ı tek satırla okuyup dışarı yollar.
Saldırgan onu kendi makinesinde süresi dolana kadar kullanır. OWASP'ın HTML5 güvenlik rehberi bu yüzden oturum kimliğini Web Storage'da tutmamanı söyler. Tek bir XSS, localStorage ve sessionStorage'daki bütün veriyi çalabilir.
Önerilen yol HttpOnly, Secure ve SameSite taşıyan bir çerezdir. Diğer yol, ön uç için arka uç desenidir (Backend for Frontend, BFF). Bu desende token'lar sunucuda kalır, tarayıcı yalnız oturum çerezi taşır.
HttpOnly çerez XSS'i yok etmez, etkisini küçültür. Betik kurbanın sekmesinden istek atabilir ama token'ı dışarı çıkaramaz. Asıl savunma yine XSS'i önlemektir.
Son olarak sık hataları topla. exp alanı olmayan token hiç ölmez. aud denetimi yapmayan servis, başka servis için üretilmiş token'ı kabul eder. Token'ı ya da oturum kimliğini günlüğe yazmak, günlüğü okuyan herkese oturum verir.
Girişte kimliği yenilememek sabitlemeye kapı açar. Durum değiştiren bir işlemi GET ile yapmak SameSite korumasını boşa çıkarır. Bu hataların çoğu kod incelemesinde birkaç satırla yakalanır. İnceleme listene bu beş maddeyi ekle.
Etkinlik 8 / 8
Doğru mu, yanlış mı?
Bir JWT'yi localStorage yerine HttpOnly çerezde saklamak, XSS sırasında token'ın tarayıcı betiğiyle okunmasını engeller.
Okumayı istediğin sırayla sürdürebilirsin. Ders, bütün etkinlikleri cevapladığında tamamlanır.
- Etkinlik 1· İki model: sunucu oturumu ve JWT
- Etkinlik 2· İki model: sunucu oturumu ve JWT
- Etkinlik 3· İmzayı doğru doğrula
- Etkinlik 4· İmzayı doğru doğrula
- Etkinlik 5· Çerezi sertleştir, kimliği yenile
- Etkinlik 6· Çerezi sertleştir, kimliği yenile
- Etkinlik 7· Oturum ömrü, çıkış ve iptal
- Etkinlik 8· Token'ı tarayıcıda nerede tutarsın