OAuth 2.0 ve OIDC: istemci tarafı hataları ve PKCE
- OAuth yetkilendirme kodu akışını adım adım izlemeyi, redirect_uri, state, nonce ve PKCE denetimlerini ve id_token doğrulamasını kodda kurmayı öğrenirsin.
- Bu derste bir OAuth akışının isteklerini satır satır okur, PKCE değerini openssl ile hesaplar ve id_token'ı PyJWT ile doğrularsı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.
Birçok uygulama kullanıcıyı kendi parolasıyla değil, bir kimlik sağlayıcısı üzerinden içeri alır. Arka planda çoğu zaman OAuth 2.0 ve OpenID Connect (OIDC) çalışır.
Protokolün kendisi sağlamdır. Sızıntıların büyük bölümü istemci tarafındaki küçük ihmallerden doğar: gevşek bir adres denetimi, atlanan bir state kontrolü ya da doğrulanmayan bir id_token. Bu derste Altay Kargo'nun kurye uygulaması üzerinden ilerlersin.
Önce akışın isteklerini tek tek okursun. Sonra redirect_uri, state, nonce ve PKCE denetimlerini kurarsın. En sonda id_token'ı kodda doğrular ve eski akışların neden bırakıldığını görürsün.
Roller ve yetkilendirme kodu akışı
OAuth 2.0 bir yetkilendirme (delegasyon) protokolüdür. Kullanıcı, verisine erişim yetkisini parolasını paylaşmadan başka bir uygulamaya devreder. OAuth kimlik doğrulama protokolü değildir; tek başına bu kullanıcı kim sorusunu yanıtlamaz. OIDC, OAuth'un üstüne bir kimlik katmanı ekler ve id_token adında imzalı bir kimlik belgesi döner.
Akışta dört taraf vardır: kullanıcı, istemci uygulama, yetkilendirme sunucusu ve kaynak sunucusu. Altay Kargo'nun kurye uygulaması, kullanıcının adres defterine erişmek için bu dört tarafı kullanır. Uygulama kullanıcının parolasını hiç görmez, yalnız yetkilendirme sunucusunun verdiği token'ı taşır.
Yetkilendirme kodu akışı dört adımda ilerler. İstemci kullanıcıyı yetkilendirme sunucusuna yönlendirir. Kullanıcı giriş yapar ve istenen yetkiyi onaylar.
Sunucu kısa ömürlü bir yetki kodunu önceden kayıtlı redirect_uri adresine yollar. İstemci bu kodu token uç noktasında access token'a çevirir. Kod tek başına erişim vermez.
RFC 6749 kodun en çok on dakika yaşamasını önerir ve kodun tek kez kullanılmasını şart koşar. Aynı kod ikinci kez gelirse sunucu isteği reddeder. Aşağıdaki iz, kurye uygulamasının bir girişini üç istek hâlinde gösterir.
# 1. İstemci tarayıcıyı yetkilendirme ucuna yollar
GET /yetkilendir?response_type=code&client_id=kurye-mobil
&redirect_uri=https%3A%2F%2Fkurye.altaykargo.example%2Fgeri-don
&scope=openid%20adres.oku&state=Vq3xT0bN8cLk2PzR&nonce=n7Hq2Lx9Wd4Pz1Ks
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256 HTTP/1.1
Host: kimlik.altaykargo.example
# 2. Kullanıcı onaylar, sunucu kodu kayıtlı adrese yollar
HTTP/1.1 302 Found
Location: https://kurye.altaykargo.example/geri-don?code=SplxlOBeZQQYbYS6WxSbIA&state=Vq3xT0bN8cLk2PzR
# 3. İstemci kodu token'a çevirir
POST /token HTTP/1.1
Host: kimlik.altaykargo.example
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&client_id=kurye-mobil
&redirect_uri=https%3A%2F%2Fkurye.altaykargo.example%2Fgeri-don
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkİzi parametre parametre oku. response_type=code, yetkilendirme kodu akışını seçer. client_id uygulamayı, scope istenen yetkiyi tanımlar. openid kapsamı OIDC'yi açar, adres.oku yalnız okuma ister.
redirect_uri, kodun gideceği tek adrestir ve kayıtlı değerle karşılaştırılır. state, dönüşte aynen geri gelir ve istemciyi CSRF'e karşı korur. nonce, id_token'ın içine yazılır ve token'ın bu girişe ait olduğunu kanıtlar. code_challenge, PKCE'nin açık tarafıdır; S256, bunun bir SHA-256 özeti olduğunu söyler.
İkinci adımda kod ve state kayıtlı adrese döner. Üçüncü adımda istemci kodu, gizli tuttuğu code_verifier ile birlikte gönderir. Her parametre ayrı bir saldırıyı kapatır. Bölümün geri kalanında bunları tek tek açarsın.
Etkinlik 1 / 8
Akışta, istemcinin yetki kodunu access token'a çevirdiği durağı işaretle.
Diyagramda doğru kutuyu tıkla.
Etkinlik 2 / 8
Kurye uygulamasının PKCE'li giriş akışını doğru sıraya koy.
Bir öğeye odaklan, yukarı ve aşağı ok tuşlarıyla taşı. Home tuşu en üste, End tuşu en alta alır.
- 1İstemci kodu ve code_verifier değerini token ucuna gönderir
- 2İstemci dönen state değerini oturumdaki kopyayla karşılaştırır
- 3İstemci id_token'ı doğrular, access token ile API'yi çağırır
- 4İstemci state, nonce ve code_challenge üretip kullanıcıyı yetkilendirme ucuna yollar
- 5Kullanıcı kimlik sunucusunda giriş yapar ve istenen kapsamı onaylar
- 6Sunucu yetki kodunu ve state değerini kayıtlı redirect_uri adresine yollar
redirect_uri: tam dize, joker yok
redirect_uri akıştaki en önemli güvenlik sınırıdır. Sunucu kodu yalnız kayıtlı adrese gönderir. Doğrulama ön ek (prefix) ya da alt dize eşleşmesiyle gevşek yapılırsa saldırgan bu sınırı aşar.
Kayıtlı adrese benzeyen bir alan adı alır ya da kayıtlı adresin devamına kendi yolunu ekler. Kurbanı hazırladığı bağlantıya tıklatır ve kod onun sunucusuna gider. Joker karakter (wildcard) kullanan kayıt da aynı riski taşır.
RFC 9700 bu yüzden tam dize eşleşmesini zorunlu tutar. Tek istisna, yerel uygulamaların localhost adresindeki değişken port numarasıdır. Bir istemci birden çok adres kaydedebilir, ama her biri tam ve sabit bir dizedir.
KAYITLI_ADRESLER = {
"kurye-mobil": {"https://kurye.altaykargo.example/geri-don"},
}
def redirect_uri_gecerli(client_id, redirect_uri):
# Hatalı: redirect_uri.startswith("https://kurye.altaykargo.example")
return redirect_uri in KAYITLI_ADRESLER.get(client_id, set())Yorum satırındaki hatalı denetim, https://kurye.altaykargo.example.saldirgan.example/geri-don adresini de kabul ederdi. Bu adres saldırganın alan adıdır, yalnız başı tanıdık görünür. Doğru kod adresi kümede arar ve tek karakter farkında bile reddeder. Denetimi yetkilendirme sunucusu yapar, istemci de kendi dönüş ucunda aynı kuralı uygular.
İkinci risk açık yönlendirmedir (open redirect, CWE-601). İstemcide gelen adrese körü körüne yönlendiren bir uç varsa, kod ya da token bu zincirle dışarı taşınabilir. RFC 9700 hem istemcinin hem sunucunun açık yönlendirici barındırmamasını ister.
Tespit için reddedilen her redirect_uri değerini client_id ile günlüğe yaz. Kısa sürede çok sayıda ret, birinin sınırı yokladığını gösterir.
Mobil uygulamada dönüş adresi ayrı bir dikkat ister. kurye:// gibi özel bir adres şemasını (custom URI scheme) cihazdaki başka bir uygulama da kaydedebilir. O zaman kod yanlış uygulamaya düşer. RFC 8252 bu riske iki yanıt verir.
Birincisi, işletim sisteminin alan adıyla doğruladığı https dönüş adresidir; kurye uygulaması bu yüzden https://kurye.altaykargo.example/geri-don kullanır. İkincisi PKCE'dir. RFC, genel istemci olan yerel uygulamada PKCE'yi zorunlu tutar; kodu yakalayan uygulama verifier'ı bilmediği için onu token'a çeviremez.
RFC ayrıca girişin sistem tarayıcısında yapılmasını ister ve uygulamanın içine gömülü web görünümünü açıkça yasaklar. Gömülü görünümde uygulama, kullanıcının yazdığı parolayı okuyabilir. Sistem tarayıcısında parola yalnız kimlik sunucusuna gider.
Etkinlik 3 / 8
Altay Kargo'nun yetkilendirme sunucusu redirect_uri değerini yalnız ön ek (prefix) eşleşmesiyle doğruluyor. Bunun asıl riski nedir?
Etkinlik 4 / 8
kimlik.altaykargo.example üzerindeki /geri-don ucuna yalnız tam eşleşen redirect_uri değerlerini yakalayan bir düzenli ifade yaz.
Yazdıkça eşleşen satırlar işaretlenir. Hedef: yalnız doğru satırlar eşleşsin.
state, nonce ve PKCE
state parametresi CSRF'e karşı çalışır. İstemci yönlendirmeden önce rastgele bir state üretir ve kullanıcının oturumuna kaydeder. Sunucu bu değeri kodla birlikte geri yollar, istemci de eşleşmeyi denetler.
Kontrol atlanırsa saldırgan kendi hesabıyla başlattığı akışın kodunu kurbana yollar. Kurban bağlantıya tıklarsa uygulama, saldırganın hesabını kurbanın oturumuna bağlar. Kurbanın sonra girdiği veriler saldırganın hesabına akar.
İyi bir state tek kullanımlık, oturuma bağlı ve kısa ömürlüdür. MCP güvenlik rehberi on dakikayı örnek verir. nonce ise id_token'a yazılır. İstemci dönen token'daki nonce'u kendi kopyasıyla karşılaştırır ve eski bir id_token'ın yeniden oynatılmasını keser.
PKCE (Proof Key for Code Exchange), araya giren birinin çaldığı kodu değersiz kılar. İstemci her girişte code_verifier adında rastgele bir değer üretir. Değer 43 ile 128 karakter arasındadır ve 32 rastgele bayttan türetilmesi önerilir.
İstemci bunun SHA-256 özetini alır, Base64URL ile kodlar ve code_challenge olarak yollar. Token değişiminde asıl verifier'ı gönderir. Sunucu verifier'ın özetini hesaplar ve kayıtlı challenge ile karşılaştırır.
Kodu çalan saldırgan verifier'ı bilmez, özetten de geri bulamaz. Yöntem olarak S256 kullan. plain yönteminde challenge verifier'ın kendisidir ve yetkilendirme isteği sızarsa koruma kalmaz.
$ V='dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk' $ printf %s "$V" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=' E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM $ openssl rand 32 | openssl base64 | tr '+/' '-_' | tr -d '=' kH3v0Qp8xNcY2mLr7TbW5eJd1ZsA9uFgqI4oVn6RtEy
Zinciri adım adım oku. printf %s verifier'ı satır sonu eklemeden verir; satır sonu özeti değiştirirdi. openssl dgst -sha256 -binary 32 baytlık ham özeti üretir.
openssl base64 onu standart Base64'e çevirir. İlk tr, + ve / karakterlerini - ve _ ile değiştirir.
İkinci tr, sondaki = dolgusunu siler. Çıkan değer RFC 7636'nın Ek B örneğindeki challenge ile birebir aynıdır. Kendi PKCE kodunu yazarsan önce bu bilinen değerle test et.
İkinci komut yeni bir verifier üretir. 32 rastgele bayt, dolgusuz Base64URL'de 43 karakter eder; senin çıktın farklı olur. Verifier yalnız istemcide ve yalnız bu giriş boyunca yaşar.
Etkinlik 5 / 8
Uygulamaları güvenli ve güvensiz OAuth istemci pratiğine göre ayır.
Önce bir seçeneğe, sonra aşağıdaki uygun kutuya bas. İstersen seçeneği kutuya sürükleyip bırak. Yerini değiştirmek için aynı işlemi tekrarla.
0/4 seçenek yerleşti.
Etkinlik 6 / 8
Bir code_verifier değerinden S256 code_challenge üreten komut zincirini kur. Son iki adımda önce karakterleri çevir, sonra dolguyu sil.
Parçalara tıklayarak komutu soldan sağa kur.
id_token'ı doğrula, kapsamı daralt
OIDC'de istemci kullanıcıyı id_token'a bakarak tanır. Bu yüzden id_token'ı kullanmadan önce OIDC Core belgesinin ID Token Validation bölümündeki denetimleri yaparsın. İmza, kimlik sunucusunun yayımladığı anahtarlarla doğrulanır.
iss, beklenen kimlik sunucusuyla birebir aynıdır. aud, senin client_id değerini içerir. exp geçmemiştir, iat makul bir zamandır.
İstekte nonce yolladıysan token'daki nonce onunla eşleşir. Access token'ı kimlik kanıtı sayma. Access token kaynak sunucusu içindir ve istemci için çoğu zaman anlamsız bir dizedir. Başka bir uygulama için alınmış bir access token'ı sana getiren biri, o kullanıcı olduğunu kanıtlamış olmaz.
import hmac
import jwt # PyJWT 2.x
JWKS = jwt.PyJWKClient("https://kimlik.altaykargo.example/.well-known/jwks.json")
def id_token_dogrula(id_token, beklenen_nonce):
anahtar = JWKS.get_signing_key_from_jwt(id_token) # kid ile kendi JWKS adresinden
iddialar = jwt.decode(
id_token,
anahtar.key,
algorithms=["RS256"],
audience="kurye-mobil", # aud: senin client_id'n
issuer="https://kimlik.altaykargo.example",
options={"require": ["exp", "iat", "sub", "nonce"]},
)
if not hmac.compare_digest(iddialar["nonce"], beklenen_nonce):
raise ValueError("nonce eşleşmedi")
return iddialar["sub"]Kodu satır satır oku. PyJWKClient, kimlik sunucusunun genel anahtar listesini (JWKS) senin yazdığın sabit adresten çeker. Token'ın kid alanı yalnız bu listeden hangi anahtarın seçileceğini söyler. Anahtar adresini token'dan almaz.
decode imzayı, algoritmayı, aud, iss ve exp değerlerini birlikte denetler. aud denetimini kapatan bir seçenek, örneğin PyJWT'de verify_aud False, başka uygulama için verilmiş token'ı da geçirir. require listesi eksik iddiayı reddeder. nonce karşılaştırması sabit zamanlı yapılır.
Fonksiyon kullanıcı kimliği olarak sub değerini döner. Kullanıcıyı e-posta ile değil, iss ve sub ikilisiyle tanı. E-posta değişebilir ve her sağlayıcıda doğrulanmış olmayabilir.
Kapsam ve hedef kitle de aynı ilkeye bağlanır. İstemci yalnız ihtiyacı olan en dar kapsamı ister. Yeni bir yetki gerekince onu o anda, artımlı olarak ister. Kaynak sunucusu da gelen access token'ın aud değerine bakar.
Token kendi API'si için üretilmemişse reddeder. MCP yetkilendirme belirtimi bu kuralı açıkça koyar.
Bir MCP sunucusu, kendisi için verilmemiş token'ı kabul etmez ve istemciden aldığı token'ı aşağıdaki API'ye aynen aktarmaz. Bu yasak token aktarımı (token passthrough) adını taşır. Aktarılan token, hız sınırı ve denetim kaydı gibi kontrolleri atlatır.
Etkinlik 7 / 8
Kurye uygulamasının id_token doğrulamasında, başka bir uygulama için üretilmiş token'ı da kabul ettiren satırı bul.
Eski akışlar, yenileme ve sık hatalar
Eski implicit akış, access token'ı doğrudan tarayıcının adres satırında döndürüyordu. Token tarayıcı geçmişinde, yönlendirme başlıklarında ve sayfadaki üçüncü taraf betiklerde iz bırakıyordu. RFC 9700 implicit akışın kullanılmamasını önerir.
Kullanıcının parolasını istemcinin topladığı parola akışını (Resource Owner Password Credentials) ise tamamen yasaklar. İkisinin yerine PKCE'li yetkilendirme kodu akışı gelir.
Altay Kargo'nun mobil uygulaması da geçen yıl implicit akıştan PKCE'li kod akışına geçti. Yenileme token'ı da korunmalıdır. Genel istemcide token ya döndürülür ya da göndericiye bağlanır. DPoP ya da karşılıklı TLS (mTLS), token'ı istemcinin anahtarına bağlar; çalınan token başka makinede işe yaramaz.
Sık hataları topla. redirect_uri'de joker ya da ön ek eşleşmesi kodu saldırgana yollar. İstemcideki açık yönlendirme, sıkı bir redirect_uri'yi bile delebilir. Sabit ya da tahmin edilebilir bir state, CSRF korumasını kâğıt üstünde bırakır.
Doğrulanmamış e-postayla hesap birleştirmek de tehlikelidir. Kimlik sağlayıcısında kurbanın e-postasıyla hesap açan biri, e-posta eşleşmesi yüzünden kurbanın hesabına girer.
email_verified iddiasını denetle ya da hesapları yalnız iss ve sub ile bağla. Aynı sıkı doğrulama webhook adreslerinde de geçerlidir. Webhook hedefi de bir yönlendirme adresidir. Gevşek doğrulanırsa saldırgan kendi sunucusunu hedef yapar ve olay verisini kendine çeker.
Bu hataları sahada günlükle yakalarsın. Dönüş ucunda state eşleşmezse isteği reddet ve olayı kaydet. Yetkilendirme sunucusu, kayıtlı olmayan redirect_uri değerlerini ve token ucunda başarısız PKCE denetimlerini (invalid_grant) client_id ile birlikte yazar. id_token doğrulaması düşerse hatanın türünü de kaydet: imza, aud, iss ya da nonce.
Tek bir ret çoğu zaman kullanıcı hatasıdır. Aynı client_id için kısa sürede biriken retler ise birinin akışı yokladığını gösterir. Bu kayıtlara token, kod ya da verifier değerini yazma; yalnız türünü ve zamanını yaz.
Derinleş:Kısa tarih: RFC 6749'dan OAuth 2.1'e
OAuth 2.0, Ekim 2012'de RFC 6749 ile yayımlandı. O yıllarda tarayıcılar başka alan adına JavaScript isteğini (CORS) yaygın desteklemiyordu. Tek sayfa uygulamalar token ucuna istek atamadığı için implicit akış doğdu ve token adres parçasında (fragment) döndü.
Ocak 2013'te RFC 6819 protokolün tehdit modelini yazdı. Mobil dünyada başka bir uygulamanın aynı özel adres şemasını kaydedip kodu yakalayabildiği görüldü. Eylül 2015'te RFC 7636 bu saldırıya karşı PKCE'yi tanımladı.
Ekim 2017'de RFC 8252, yerel uygulamalar için sistem tarayıcısını ve PKCE'yi en iyi uygulama yaptı. Ocak 2025'te RFC 9700, on yılın derslerini BCP 240 olarak topladı. OAuth 2.1 taslağı bu kuralları tek belgede birleştiriyor: implicit ve parola akışı yok, kod akışında PKCE her istemciye zorunlu, redirect_uri tam dizeyle karşılaştırılıyor.
Derinleş:DPoP token'ı nasıl bağlar
DPoP (RFC 9449, Eylül 2023) token'ı istemcinin anahtar çiftine bağlar. İstemci bir anahtar çifti üretir ve özel anahtarı kendinde tutar. Her istekte DPoP başlığında kısa bir kanıt JWT'si yollar.
Kanıtta isteğin yöntemi (htm), adresi (htu), zamanı (iat) ve benzersiz kimliği (jti) bulunur. Kanıt özel anahtarla imzalıdır ve genel anahtarı kendi header'ında taşır. Yetkilendirme sunucusu token'ı verirken genel anahtarın parmak izini token'a yazar.
Kaynak sunucusu her istekte kanıtın imzasını, yöntemi, adresi ve parmak izinin eşleşmesini denetler. Çalınan token tek başına işe yaramaz, çünkü saldırgan özel anahtar olmadan yeni kanıt üretemez. jti kaydı da aynı kanıtın yeniden oynatılmasını keser. Karşılıklı TLS bağlama (RFC 8705) aynı sonuca istemci sertifikasıyla ulaşır.
Etkinlik 8 / 8
Doğru mu, yanlış mı?
Bir webhook hedef adresini kaydederken ön ek eşleşmesi kullanmak, redirect_uri'de olduğu kadar güvenlidir.
Okumayı istediğin sırayla sürdürebilirsin. Ders, bütün etkinlikleri cevapladığında tamamlanır.
- Etkinlik 1· Roller ve yetkilendirme kodu akışı
- Etkinlik 2· Roller ve yetkilendirme kodu akışı
- Etkinlik 3· redirect_uri: tam dize, joker yok
- Etkinlik 4· redirect_uri: tam dize, joker yok
- Etkinlik 5· state, nonce ve PKCE
- Etkinlik 6· state, nonce ve PKCE
- Etkinlik 7· id_token'ı doğrula, kapsamı daralt
- Etkinlik 8· Eski akışlar, yenileme ve sık hatalar