Aynı İsteği İki Kez Görünce: mTLS'ten DPoP'a, Retry'dan Idempotency'ye
Bir isteğin iki kez gelmesinin iki yüzü vardır: biri sizi soymak isteyen bir saldırgan, diğeri sadece "acaba gitti mi?" diye tekrar deneyen dürüst bir istemci. Bu yazıda, Apinizer'da ikisini de karşılayan iki mekanizmadan, DPoP ve Idempotency'den bahsedeceğiz.
İki Farklı Tekrar, Tek Soru
Hepimizin başına gelmiştir: Ödeme ekranında "Öde" butonuna bastınız, sayfa döndü döndü, bir türlü yanıt gelmedi. İçinizden "acaba çekildi mi çekilmedi mi?" diye geçirirken bir kez daha tıkladınız. Sonra ekstreye baktığınızda iki çekim... İşte o an, dağıtık sistemlerin en klasik problemlerinden biriyle tanışmış oldunuz: aynı isteğin iki kez işlenmesi.

Şimdi bir de şu senaryoyu düşünün: Bir saldırgan, ağ üzerinde dolaşan bir access token'ı bir şekilde ele geçirdi. Elinde geçerli bir "Bearer" token var ve Bearer token'ın doğası gereği, onu kim taşıyorsa geçerlidir. Token'ı çalan da, sanki asıl sahibiymiş gibi API'lerinize erişebilir.
İlk bakışta bu iki hikâye alakasız görünüyor. Biri bir güvenilirlik (reliability) problemi, diğeri bir güvenlik (security) problemi. Ama ikisinin de merkezinde aynı soru var:
"Bu isteği daha önce gördüm mü? Gördüysem ne yapmalıyım?"
Fark, isteği tekrarlayanın niyetinde: Biri kötü niyetli bir replay saldırısı, diğeri iyi niyetli bir retry. Bu yazıda, Apinizer'ın bu iki yüzü nasıl ele aldığına bakacağız. Önce token'ı sahibine bağlayan DPoP ile, sonra dürüst tekrarı zararsız kılan Idempotency ile.
Ve tabii, her mimari kararda olduğu gibi, cevap yine o meşhur ifadeyle başlıyor: it depends. Ama sonu, sandığınızdan daha net.
Bölüm 1: Kimliği Kanala mı, İsteğe mi Bağlamalı? mTLS'in Yükü

Bir API'yi çağıran istemcinin gerçekten "o" olduğundan nasıl emin oluruz? Yıllardır bu sorunun en güçlü cevaplarından biri mTLS (mutual TLS) oldu.
mTLS'te iş çift taraflı yürür: Nasıl istemci sunucunun sertifikasını doğruluyorsa, sunucu da istemcinin sertifikasını doğrular. Sonuç güçlüdür; kimlik, doğrudan TLS kanalının kendisine bağlanır. Kanalı kuran sertifikaya sahip değilseniz, konuşma hiç başlamaz. Bu yüzden bankacılık, açık bankacılık (ÖHVPS), savunma gibi yüksek güven gerektiren entegrasyonlarda mTLS uzun süre standart oldu.
Peki neden herkes hâlâ mutlu mesut mTLS kullanmıyor? Çünkü mTLS'in bedeli kağıt üzerinde değil, operasyonda ödeniyor.

Sertifika ömürleri kısalıyor, hem de takvimi belli
Bir zamanlar sertifikayı bir kez alır, yıllarca unuturduk; o günler bitti. CA/Browser Forum'un SC-081v3 kararıyla public TLS sertifikalarında üst sınır Mart 2026'da 398 günden 200 güne indi; Mart 2027'de 100, Mart 2029'da 47 gün olacak. Bu takvim doğrudan mTLS istemci sertifikalarını değil, publicly trusted sunucu sertifikalarını bağlıyor; ama yönü net, ve iç PKI dünyası zaten daha hızlı koşuyor: service mesh'lerde sertifikalar çoktan saatlik/günlük dönüyor. Ömür kısaldıkça, "yenileme" bir kereye mahsus bir iş olmaktan çıkıp sürekli işleyen bir makineye dönüşüyor. Bu sorun yönetimsel API'lerle otomatikleştirme ve CI/CD, DevOps gibi süreçlerin parçası haline getirilmesi ile çözümlenmeye çalışılsa da özellikle legacy servislerin durumunu ele almak halen sancılı.
Her iç uygulama bir sertifika yönetmek zorunda
İşte asıl acı burada. Onlarca, bazen yüzlerce iç servisiniz varsa, mTLS demek, bu servislerin her birinin bir sertifikaya sahip olması, onu güvenli saklaması, süresi dolmadan yenilemesi ve dağıtması demek. Bir tanesinin rotation'ı ıskalanırsa, o servis sessizce iletişimi keser. Gecenin bir yarısı "neden 500 alıyoruz?" diye başlayan çoğu incident'ın altından süresi dolmuş bir sertifika çıkar.
Kimlik kanala bağlı, istekle taşınmıyor
mTLS'in gücü aynı zamanda kısıtı: Kimlik, TLS bağlantısının kendisine aittir. Araya bir ters proxy, bir load balancer, bir ingress girip TLS'i sonlandırdığında (ki kurumsal mimaride neredeyse her zaman girer), istemci kimliği ilk hop'ta "erir". Onu backend'e taşımak için ekstra header'lar, ekstra güven zincirleri kurmak gerekir.
Kısacası mTLS güçlü ama pahalı bir çözüm. Ve bu pahalılık, her yeni iç uygulamada, her sertifika döngüsünde tekrar tekrar önünüze gelir. DRY prensibini seven birine, bu tekrar eden operasyonel yük tanıdık gelmelidir: aynı işi, her uygulamada, tekrar tekrar yapıyorsunuz.
Bölüm 2: DPoP ile Token'ı İsteğe Bağlamak (RFC 9449)

Bearer token'ın problemi tek cümleyle özetlenebilir: sender-constrained değildir. Yani token, onu taşıyan kişiye bağlı değildir. Çalınırsa, çalan kullanır.
DPoP (Demonstrating Proof-of-Possession) tam da bunu çözmek için doğdu. Fikir zarif:
- İstemci bir asimetrik anahtar çifti üretir (RSA / EC / EdDSA). Private key hep kendisinde kalır.
- Yetkilendirme sunucusundan aldığı access token, bu anahtarın açık kısmına bağlanır; token içine
cnf.jkt(anahtarın SHA-256 parmak izi) yazılır. Artık token "herhangi biri" için değil, "bu anahtara sahip olan" içindir. - İstemci her API isteğine, private key ile imzaladığı kısa ömürlü bir DPoP proof'u ekler. Bu proof bir HTTP header'ında (
DPoP:) gider ve şunları söyler: "Bu isteği ben yaptım, işte kanıtı."
Proof'un içinde neler var?
htm: İsteğin HTTP metodu (POST, GET...). Proof'u o metoda bağlar.htu: İsteğin gittiği URI. Proof'u o adrese bağlar.iat: Üretim zamanı. Çok eski proof'lar reddedilir.jti: Proof'un benzersiz kimliği. Aynı proof'un ikinci kez kullanımını engeller (replay koruması).ath: Access token'ın SHA-256 özeti. Proof'u yalnızca o token ile geçerli kılar.cnf.jkt(token'da): Token'ın hangi anahtara bağlı olduğu. DPoP'un asıl koruması.

Şimdi saldırganın hikâyesine geri dönelim: Token'ı çaldı diyelim. Ama private key hâlâ asıl sahibinde. Saldırgan geçerli bir proof üretemez, çünkü imzalayamaz. Token'ı kullanmaya kalkarsa, sunucu cnf.jkt ile proof anahtarının eşleşmediğini görür ve isteği reddeder. Çalınan token, proof olmadan ölü bir kağıt parçasıdır.
Ve işin güzeli: bütün bunlar HTTP katmanında, standart header'larla oluyor. TLS'e dokunmuyor, sertifika dağıtmıyor, PKI zinciri kurmuyoruz. Bir nevi "HTTPS üzerinde, uygulama seviyesinde proof-of-possession". Bir mobil uygulama, bir SPA, bir iç mikroservis; hepsi kendi anahtar çiftini üretip proof gönderebilir. Kimsenin bir sertifika otoritesine kaydolmasına, dağıtım altyapısı kurmasına gerek yok.
mTLS mi, DPoP mu? İşte tam da "it depends" anı
Burada dürüst olalım: DPoP, mTLS'i her senaryoda "öldüren" bir teknoloji değil. İkisi farklı katmanlarda çalışır ve bazen birlikte de kullanılırlar.
mTLS hâlâ doğru tercih olabilir, eğer:
- Topolojiniz sabit, taraflar az ve güven çok yüksekse,
- Zaten olgun bir PKI / service mesh altyapınız varsa,
- Kanal seviyesinde, en alttan gelen bir garantiye ihtiyacınız varsa.
DPoP daha pragmatik olur, eğer:
- Kimlik doğrulaması gereken çok sayıda iç uygulama/istemci varsa ve her birine sertifika dağıtmak istemiyorsanız,
- Kısalan sertifika ömürlerinin operasyonel yükünden kaçınmak istiyorsanız,
- İstemcileriniz mobil/SPA gibi sertifika saklamanın zor olduğu ortamlardaysa,
- Kimliğin, TLS sonlandıran her ara katmanda erimesini değil, istekle birlikte uçtan uca taşınmasını istiyorsanız.
Çoğu kurumun gerçekliği şudur: Onlarca iç uygulamayı tek tek mTLS'e entegre etmenin, her birinin sertifika döngüsünü yönetmenin maliyeti, sağladığı ek güvenlikten daha ağır basmaya başlar. İşte o eşik geçildiğinde, DPoP "anı kurtaran pragmatik çözüm" değil, doğrudan "daha doğru mimari karar" olur.
Kısa ömürlü imzalı nesnelerle çalışırken en sinsi arıza kriptografik değil, kronolojiktir. Bir entegrasyonda imza doğrulamalarının rastgele düşmesinin sebebi, sunucular arasındaki dört dakikalık saat kaymasıydı; imza doğruydu, zaman penceresi değil. DPoP'ta iat kontrolü de aynı sınıfa girer: proof'u doğrulayan tarafta NTP senkronizasyonu ve saat sapması üzerine bir alarm, opsiyonel değil ön koşuldur.
Bölüm 3: Aynı Madalyonun Diğer Yüzü: Idempotency

Şimdi yazının başındaki çift-tıklama hikâyesine dönelim. Bu sefer ortada saldırgan yok; sadece dürüst bir istemci var ve o da aynı isteği iki kez gönderdi. Neden?
- Timeout: Yanıt gelmeden istemci vazgeçti ve tekrar denedi.
- Retry: Ağ geçici bir hıçkırık yaşadı, kütüphane otomatik yeniden gönderdi.
- Çift tıklama: Kullanıcı sabırsızlandı.
- Load balancer / proxy: Bir yerlerde bir isteği failover ile ikinci kez yolladı.
GET gibi idempotent (tekrarı zararsız) metotlarda bunun önemi yok. Ama POST ile para çekiyor, sipariş oluşturuyor, açık bankacılıkta bir rıza kaydediyorsanız; aynı isteğin iki kez işlenmesi felaket demektir. Çift çekim, çift sipariş, çift rıza.
Çözüm deseni yıllardır bilinen bir şey (Stripe'ın meşhur ettiği, IETF'in taslak standarda taşıdığı Idempotency-Key deseni):
- İstemci her mantıksal işlem için benzersiz bir anahtar üretir ve isteğe ekler.
- Apinizer ilk isteği işler, backend'in yanıtını saklar.
- Aynı anahtarla ikinci istek gelince, Apinizer backend'i hiç rahatsız etmeden saklı yanıtı geri döner.
Sonuç: İstemci ne kadar tekrar gönderirse göndersin, işlem istemci açısından tek bir kez gerçekleşmiş olur. Buna bilerek "exactly-once" demiyoruz; dağıtık bir sistemde ağ isteği her zaman at-least-once taşır. Yapılan şey, tekrarı en uçta emip etkisini tek sefere indirmektir.
Dikkat ettiyseniz, DPoP ile idempotency, aynı altyapının iki farklı kullanımı:
- DPoP, bir
jti'nin daha önce görülüp görülmediğini dağıtık cache'te kontrol ederek saldırıyı reddeder. - Idempotency, bir key'in daha önce görülüp görülmediğini dağıtık cache'te kontrol ederek dürüst tekrarı zararsız kılar.
İkisi de "bu isteği daha önce gördüm mü?" sorusunu, dağıtık bir hafızada yanıtlıyor. Sadece bulduklarında verdikleri cevap farklı:
DPoP "seni tanıdım, defol" der; idempotency "seni tanıdım, işte ilk seferki yanıtın" der.
Güvenlik ile güvenilirlik, aynı madalyonun iki yüzü.
Bölüm 4: Peki Apinizer'da?

İşte burada Apinizer'ın en sevdiğimiz felsefesi devreye giriyor: cross-cutting concern'leri merkezileştir. Ne token'ı kanıta bağlamak, ne de tekrarı yakalamak, iş uygulamanızın asıl işi değildir. Bunları her uygulamaya tek tek yazmak, DRY prensibinin ders kitabı ihlalidir. Apinizer bu iki ihtiyacı da, birer ayar olarak karşılar; tek satır kod yazmadan.
DPoP tarafında güzel olan şu: Apinizer trafiğin hem gelen hem giden ucunda durduğu için DPoP'un iki rolünü de üstlenebiliyor. Arkasındaki bir servis dışarıdaki DPoP korumalı bir API'ye (mesela bir açık bankacılık servisine) çağrı yapacaksa, o çağrının proof'unu JOSE Implementation politikasında Apinizer üretir. Tersine, Apinizer'ın koruduğu bir API'ye DPoP ile bir istemci geliyorsa, gelen proof'u ve token'ın gerçekten o anahtara bağlı olduğunu JOSE Validation politikasında doğrular; ve aynı proof'un ikinci kez kullanılmasını, gördüğü kimlikleri dağıtık cache'te tutarak engeller. Replay koruması tam burada devreye girer. Özetle Apinizer dışarıya kanıt üretir, içeriye geleni doğrular; uygulamalarınızın hiçbiri DPoP'un ne olduğunu bilmek zorunda kalmaz.
Bir noktayı netleştirelim: DPoP'ta token'ı anahtara bağlayan claim (cnf.jkt) token üretilirken yetkilendirme sunucusu tarafından yazılır; Apinizer bu bağlamanın gerçekten kurulduğunu ve proof'un o anahtara ait olduğunu doğrular. Yani zincirin uçtan uca tamam olması için token'ı üreten tarafın da DPoP'u desteklemesi gerekir; mimarinizi kurarken ilk bakacağınız yer burasıdır.
Keycloak'tan DPoP-bound token alıp Apinizer JOSE Validation politikasında proof zincirini adım adım doğrulamak için Keycloak'tan DPoP Token Almak ve Apinizer JOSE Validation ile Doğrulamak makalesini inceleyebilirsiniz.
Idempotency tarafında ise ayar bir politika değil, doğrudan API Proxy üzerindedir; çünkü tekrarı yakalamak tüm proxy'yi ilgilendiren bir davranıştır. Anahtarı ister istemcinin gönderdiği bir header'dan (Idempotency-Key) okutursunuz, ister işin kendi verisinden (açık bankacılıkta bir rıza numarası gibi) türetirsiniz. Gerisini Apinizer halleder: ilk isteği işler, yanıtını saklar; aynı anahtarla gelen tekrarları backend'i hiç yormadan o saklı yanıtla karşılar. İşlem, istemci kaç kez denerse denesin, istemci açısından tek bir kez gerçekleşir. Tabi Apinizer'ın işi yalnızca yanıtı saklayıp geri vermek değil. Aynı anahtarla ikinci istek, birincisi hâlâ işlenirken gelirse yarış durumu ayrıca ele alınır; aynı anahtarın farklı bir içerikle kullanılması ise sessizce yanlış yanıt dönmemesi için reddedilir. Bir idempotency implementasyonunu demodan ayıran şey, pratikte tam olarak bu iki ayrıntıdır.
İkisinin de ortak güzelliği: hiçbiri için kod yazmazsınız. YAGNI; ihtiyacınız olan yerde açar, olmayanda hiç dokunmazsınız. Ödeme akışınız bunları ister, basit bir sorgu servisi istemez; ikisine de aynı gateway, farklı ayarlarla hizmet eder.
Sonuç: It Depends, Ama Operasyonel Gerçeklik Bir Yönü İşaret Ediyor
Bu yazıyı iki iddia ile bağlayalım.
Birincisi güvenlik tarafında: mTLS güçlü bir araçtır ve doğru yerde hâlâ vazgeçilmezdir. Ama "kimlik doğrulaması gereken her iç uygulamaya sertifika dağıtma, sürekli kısalan ömürlerle bunları döndürme, her TLS sonlandırmasında kimliği yeniden taşıma" yükü belli bir ölçekten sonra taşınamaz hale gelir. DPoP, kimliği kanala değil isteğe bağlayarak, bu yükün çoğunu ortadan kaldırır. Sertifika altyapısı kurmadan, HTTP katmanında, çalınan token'ı işe yaramaz hale getirir. Çok sayıda iç entegrasyonu olan çoğu kurum için bu, "pragmatik ödün" değil, daha temiz mimaridir.
İkincisi güvenilirlik tarafında: Idempotency, para/rıza/sipariş gibi non-idempotent akışlarda tercih değil, zorunluluktur. Ağ bir gün mutlaka bir isteği tekrarlayacak; soru "olur mu" değil, "olduğunda ne olacak". Cevabınız "Apinizer zaten tanıyıp zararsız kılıyor" olmalı.
İki mekanizmanın da ortak dersi şu: Aynı isteğin iki kez gelmesi kaçınılmazdır. Fark, o tekrarı kimin gönderdiğinde. DPoP düşman tekrarını reddeder, idempotency dürüst tekrarı bağışlar; ama ikisi de "bu isteği daha önce gördüm mü?" sorusunu, uygulamanızın omuzlarından alıp Apinizer'ın dağıtık hafızasına yıkar.

Apinizer'ın bütün duruşu zaten burada özetleniyor: Güvenlik ve güvenilirlik, her API'de tekrar tekrar çözülmesi gereken cross-cutting concern'lerdir. Onları uygulamalarınıza dağıtmak yerine tek noktada, ayar seviyesinde çözersiniz. Uygulamalarınız asıl işlerine, iş mantığına odaklanır. Tekerleği yeniden icat etmek yerine, var olanı kullanıp yola devam edersiniz.
Ve evet, hangisini ne zaman kullanacağınız yine "it depends" ile başlar. Ama parametreleri (kaç iç uygulamanız var, sertifika yükünü kaldırabiliyor musunuz, akışınız tekrara ne kadar hassas) masaya koyduğunuzda, cevap çoğu zaman göründüğünden daha net çıkar.
Çünkü sonuçta, iyi mimari sadece "çalışan" değil; güvenli, güvenilir ve sürdürülebilir olandır.
Bu makale ilk olarak Apinizer Medium yayınında yayınlanmıştır.