Mobil bir uygulamanın arkasındaki API'yi tasarlarken en büyük hata, istemciyi güvenilir bir taraf gibi ele almaktır. Oysa cihaz kullanıcının elinde, ikili dosya decompile edilebilir, trafik bir proxy üzerinden izlenebilir ve içine gömülen her secret er ya da geç ortaya çıkar; bu yüzden mobil backend API güvenliği rate limiting, token rotasyonu ve sunucu tarafı doğrulama olmadan asla tam sayılmaz. Bu rehberde tehdit modelinden çok katmanlı rate limiting'e, kısa ömürlü access token + rotasyonlu refresh token mimarisinden çalınmış token tespitine kadar üretimde çalışan bir savunma hattını adım adım kuracağız.
💡 Pro Tip: Rate limiting'i tek katmanda (yalnızca IP) kurarsan, NAT arkasındaki yüzlerce kullanıcıyı tek kova sanıp cezalandırırken, kimlik doğrulamalı bir saldırıyı fark etmeden geçirebilirsin — katmanları her zaman IP + kullanıcı + endpoint üçlüsüyle birlikte kur.
İçindekiler
- Tehdit modeli: mobil istemci güvenilmez bir istemcidir
- Rate limiting katmanları (IP, kullanıcı, endpoint) ve doğru pencere seçimi
- Token mimarisi: kısa ömürlü access + rotasyonlu refresh
- Rotation'da çalınmış token tespiti (reuse detection)
- Brute-force ve hesap kilitleme politikası
- Secret'ları istemciden çıkarmak: proxy endpoint deseni
- Abuse sinyalleri ve alarm eşikleri
- Log'da PII bırakmamak (KVKK/GDPR)
- SSS
- Mobil uygulama API'sinde rate limiting nasıl kurulur?
- Refresh token rotation nasıl uygulanır?
- API anahtarını mobil uygulamada saklamak neden güvenli değil?
- Brute-force ve abuse'ü sunucuda nasıl tespit ederim?
- IP bazlı rate limiting neden tek başına yetmez?
- JWT mi opak token mı kullanmalıyım?
- Sonuç
- Kaynaklar
Tehdit modeli: mobil istemci güvenilmez bir istemcidir
Sunucu tarafı bir API yazarken alışkın olduğun tehdit modeli, mobil bir istemciyle karşılaştığında değişir. Web'de tarayıcı en azından aynı-origin politikası ve CSP gibi tarayıcı-taraflı korumalarla gelir; mobil uygulamada ise sen kendi "tarayıcını" da yazmış olursun ve onun üstünde hiçbir garanti yoktur.
Uygulama ikilisi kullanıcının cihazında çalışır, bir saldırgan onu indirip decompile edebilir, mitmproxy veya Charles gibi araçlarla trafiği araya girip okuyabilir, hatta uygulamayı repackage edip kendi sunucularına konuşturabilir.
OWASP'ın API Security Top 10 2023 listesindeki "API2:2023 - Broken Authentication" maddesi bu riski tam olarak tarif eder: kimlik doğrulama mekanizmaları genellikle yanlış uygulanır ve saldırganların "kimlik doğrulama tokenlarını ele geçirmesine veya uygulama açıklarından faydalanmasına" izin verir. Mobil tarafta bu, özellikle client secret'ların ikiliye gömülmesi ve token'ların güvensiz depolanmasıyla somutlaşır.
RFC 6749 (OAuth 2.0) ve RFC 8252 (native app bağlamı), mobil uygulamaları kriptografik bir secret'ı gizli tutamayan "public client" olarak sınıflandırır; RFC 9700 (OAuth 2.0 Security Best Current Practice) bu client tipi için PKCE kullanımını zorunlu koşar: "Public clients MUST use PKCE [RFC7636] to this end, as motivated in Section 4.5.3.1." Pratikte bu, backend'ini tasarlarken şu varsayımla başlaman gerektiği anlamına gelir: istemciden gelen her istek, aksi ispatlanana kadar şüphelidir. Rate limiting, token rotasyonu ve sunucu tarafı doğrulama bu varsayımın somut karşılıklarıdır — sırayla bakalım.
Rate limiting katmanları (IP, kullanıcı, endpoint) ve doğru pencere seçimi
Tek katmanlı rate limiting neredeyse her zaman yetersiz kalır. Üç katmanı birlikte düşünmek gerekir:
Katman | Ne yakalar | Tek başına zayıf yanı |
|---|---|---|
IP bazlı | Tek kaynaktan gelen yoğun trafik, basit script'ler | Mobil operatör NAT'ı arkasında binlerce meşru kullanıcı aynı IP'yi paylaşabilir |
Kullanıcı/token bazlı | Tek hesabın anormal kullanımı, kimlik bilgisi kötüye kullanımı | Saldırgan her denemede yeni hesap açarsa etkisiz kalır |
Endpoint bazlı | Login, refresh, parola sıfırlama gibi hassas uçların hedeflenmesi | Genel API trafiğini korumaz, yalnız kritik uçlara odaklanır |
OWASP'ın API4:2023 "Unrestricted Resource Consumption" maddesi rate limiting'in tek bir sabit değer olmadığını, uç nokta bazında ayarlanması gerektiğini vurgular: "Rate limiting should be fine tuned based on the business needs. Some API Endpoints might require stricter policies." Login veya parola sıfırlama gibi uçlar, genel bir listeleme uç noktasından çok daha sıkı bir pencereye ihtiyaç duyar.
Sunucu tarafında sınırı aştığında dönülecek yanıt da standarttır. OWASP'ın REST Security Cheat Sheet'i şunu söyler: "Return 429 Too Many Requests HTTP response code if requests are coming in too quickly." Bu sınırı nerede uygulayacağın da önemli: nginx JWT claim'lerini okuyamaz, dolayısıyla yalnız IP ve login katmanlarını üstlenebilir; kullanıcı bazlı katmanı doğrulanmış userId ile uygulama katmanında kurman gerekir. nginx üzerinde IP ve login katmanlarını ayrı zone'larla kurman, hangi katmanın tetiklendiğini log'lardan ayırt etmeni kolaylaştırır:
nginx
1# IP bazlı genel koruma2limit_req_zone $binary_remote_addr zone=ip_zone:10m rate=10r/s;3# Login endpoint'ine özel, daha sıkı katman4limit_req_zone $binary_remote_addr zone=login_zone:10m rate=1r/s;5 6server {7 location /api/auth/login {8 limit_req zone=login_zone burst=3 nodelay;9 limit_req zone=ip_zone burst=5;10 }11 12 location /api/ {13 limit_req zone=ip_zone burst=10;14 }15}Network Layer Optimization yazısında istemci tarafında 429 + Retry-After'ı nasıl ele aldığımı anlattım.
Pencere seçimi de aynı derecede önemli. Sabit pencere (fixed window) uygulaması basittir ama pencere sınırında bir "patlama" riski taşır: dakikanın son saniyesinde izin verilen tüm istekler atılır, yeni dakikanın ilk saniyesinde de tekrar atılabilir — kısa sürede iki katı yük oluşur. Kayan pencere (sliding window) veya token bucket yaklaşımı bu patlamayı yumuşatır çünkü sınır, sabit bir saat dilimine değil son N saniyeye bakar. Ben kritik uçlarda (login, refresh) genelde token bucket'ı tercih ediyorum; burst'e (kısa süreli patlamaya) izin verirken ortalama hızı sıkı tutuyor. nginx bunu kendi dokümanında "leaky bucket" yöntemi olarak tanımlıyor; burst parametresi de kısa süreli patlamalara izin verip fazlasını geciktirerek aynı yumuşatma etkisini veriyor.
Token mimarisi: kısa ömürlü access + rotasyonlu refresh
Mobil bir istemcide iki token türü birbirinden farklı görev üstlenir; access token her istekte taşınan geçici bir yetki kanıtı, refresh token ise arka planda yeni access token üretmek için kullanılan uzun ömürlü ama sık yenilenen bir anahtardır:
Özellik | Access token | Refresh token |
|---|---|---|
Ömür | Kısa (dakikalar) | Görece uzun, ama rotasyonla sürekli yenilenir |
Nerede saklanır | Bellekte / kısa süreli secure storage | Cihazın güvenli deposu (Keychain/Keystore) |
Her istekte kullanılır mı | Evet, her API çağrısında | Hayır, yalnız access token yenilenirken |
İptal | Süresi dolunca kendiliğinden geçersiz | Reuse tespitinde tüm aile (family) iptal edilir |
RFC 9700 public client'lar için netlik: "Refresh tokens for public clients MUST be sender-constrained or use refresh token rotation as described in Section 4.14." Sender-constraining (mTLS veya DPoP gibi) mobil tarafta kurulumu ağır bir seçenektir; bu yüzden çoğu mobil backend'i rotasyon yolunu seçer. Aynı RFC, authorization code akışında da public client'lara PKCE zorunluluğu getirir — kısacası mobil app her adımda "kanıtlanmış sahiplik" göstermek zorunda bırakılır.
JWT kullanıyorsan imza doğrulamasını asla atlama. OWASP REST Security Cheat Sheet açıkça uyarır: "Ensure JWTs are integrity protected by either a signature or a MAC. Do not allow the unsecured JWTs." Token'ın ne taşıdığını (claims) değil, sunucunun ne doğruladığını temel al.
Refresh token'ı rotasyonla değiştirdiğinde, her yeni token'ı bir "aile" (family) kimliğiyle etiketlemek işini kolaylaştırır:
json
1{2 "familyId": "fam_8f2c1a",3 "tokenId": "tok_3b91",4 "issuedAt": "2025-02-25T10:00:00Z",5 "used": false,6 "previousTokenId": "tok_1a02"7}Cihaz tarafında Keychain, SSL pinning ve jailbreak tespitini iOS Security Best Practices yazısında ele aldım; buradaki kural onun tamamlayıcısı: istemci ne söylerse söylesin sunucu doğrular.
Rotation'da çalınmış token tespiti (reuse detection)
Rotasyonun asıl gücü, sadece token'ı yenilemek değil, çalınmış bir token'ı yakalayabilmesidir. RFC 9700 mekanizmayı şöyle tarif eder: "the authorization server issues a new refresh token with every access token refresh response. The previous refresh token is invalidated, but information about the relationship is retained by the authorization server." — her refresh çağrısında eskisi geçersiz kılınır ve yerine yeni bir token verilir, ama aralarındaki ilişki sunucu tarafında saklanır. Devamındaki cümle de kritik bir sinyali işaret eder: "If a refresh token is compromised and subsequently used by both the attacker and the legitimate client, one of them will present an invalidated refresh token, which will inform the authorization server of the breach." — yani daha önce kullanılmış bir refresh token'ın tekrar sunulması bir reuse (yeniden kullanım) sinyali sayılır ve saldırı ihtimaline işaret eder.
RFC önce sunucunun "it will revoke the active refresh token" diyerek aktif token'ı iptal edeceğini söyler; implementation notunda ise bir refresh token'ın ait olduğu grant bilgisinin token'ın içine kodlanabileceğini, bunun da "all refresh tokens that need to be revoked" kümesini verimli biçimde belirlemeyi sağladığını belirtir; pratikte bu "family" mantığıyla uygulanır: aynı family'deki bir token ikinci kez kullanılmaya çalışılırsa, o family'deki TÜM token'lar (aktif olan dahil) iptal edilir ve kullanıcı yeniden kimlik doğrulamaya zorlanır.
ts
1async function rotateRefreshToken(oldToken: RefreshToken) {2 if (oldToken.used) {3 // Reuse tespit edildi: tüm aileyi iptal et, alarm üret4 await revokeTokenFamily(oldToken.familyId);5 await emitSecurityAlert("refresh_token_reuse", oldToken.familyId);6 throw new AuthError("token_reuse_detected");7 }8 9 await markTokenUsed(oldToken.tokenId);10 const next = await issueRefreshToken({11 familyId: oldToken.familyId,12 previousTokenId: oldToken.tokenId,13 });14 return next;15}Bu deseni kurduktan sonra reuse alarmlarını sessizce loglamak yetmez; bir yerde toplanıp izlenmesi gerekir — bunu aşağıdaki "abuse sinyalleri" bölümünde ele alacağım.
Brute-force ve hesap kilitleme politikası
Login ve parola sıfırlama uçları, rate limiting'in en sıkı çalışması gereken yerlerdir. OWASP'ın "API2:2023 - Broken Authentication" maddesi credential stuffing'i doğrudan tarif eder: "Permits credential stuffing where the attacker uses brute force with a list of valid usernames and passwords." Yani saldırgan senin sistemine özel bir açık aramaz; başka bir sızıntıdan aldığı kullanıcı adı/parola listesini API'ne karşı dener.
Katmanlı bir savunma şöyle görünür: önce endpoint bazlı rate limiting (yukarıdaki nginx örneğindeki login_zone), ardından hesap bazlı bir sayaç, ardından geçici kilitleme. Ben genelde art arda birkaç başarısız denemeden sonra hesabı süreli olarak kilitlemeyi, IP'yi değil hesabı hedef alarak tercih ederim — çünkü IP'yi kilitlemek paylaşımlı ağların arkasındaki meşru kullanıcıları da cezalandırır.
Bu savunma hattını kurarken CI/CD disiplini de işin bir parçası: rate limit kurallarını her deploy'da yanlışlıkla gevşetmemek için iOS CI/CD Pipeline yazısındaki release checklist yaklaşımını güvenlik kurallarına da uygulayabilirsin.
Secret'ları istemciden çıkarmak: proxy endpoint deseni
Mobil ikiliye gömülen hiçbir secret güvende değildir — obfuscation bile decompile'ı geciktirir, engellemez. Üçüncü parti bir API anahtarına ihtiyacın varsa (ödeme, harita, AI sağlayıcı fark etmez), doğru desen anahtarı istemciden değil, kendi backend'inden geçirmektir: mobil app senin API'ne konuşur, senin API'n secret'ı sunucu tarafında ekleyip üçüncü partiye iletir.
Bu proxy uç noktasını kurarken REST Security Cheat Sheet'in iki kuralı doğrudan uygulanır. HTTP metodu allowlist'i: "Apply an allowlist of permitted HTTP Methods e.g. GET, POST, PUT" ve eşleşmeyeni reddet: "Reject all requests not matching the allowlist with HTTP response code 405 Method not allowed." İstek boyutu sınırı: "Define an appropriate request size limit and reject requests exceeding the limit with HTTP response status 413."
ts
1// Basit proxy handler — secret yalnız sunucuda2// İstek gövdesi boyutu express.json({ limit: "32kb" }) ile sınırlanır (413 orada tetiklenir)3app.post(4 "/api/proxy/geocode",5 requireAuth,6 rateLimit("proxy_zone"),7 async (req, res) => {8 const { address } = req.body;9 if (typeof address !== "string" || address.length > 500) {10 return res.status(400).json({ error: "invalid_address_length" });11 }12 const upstream = await fetch(13 `${GEO_PROVIDER_URL}?key=${process.env.GEO_API_KEY}`,14 {15 method: "POST",16 body: JSON.stringify({ address }),17 },18 );19 return res.status(upstream.status).json(await upstream.json());20 },21);Bu deseni Swift Vapor ile Backend API veya REST API Tasarım Prensipleri yazısındaki ilkelerle hafif bir backend katmanında kurmak, mobil tarafta değiştirmen gereken kodu minimumda tutar — istemci hâlâ senin API'ne konuşur, sadece arkada secret bir yerde saklanır.
Abuse sinyalleri ve alarm eşikleri
Rate limiting ve token rotasyonu pasif savunma hatları; abuse tespiti ise aktif izleme gerektirir. İzlemen gereken sinyaller arasında şunlar öne çıkar: aynı hesaptan kısa sürede çok sayıda 401 (başarısız kimlik doğrulama), reuse alarmı üreten refresh token denemeleri, tek bir endpoint'e normalin çok üzerinde istek hacmi ve coğrafi olarak tutarsız ardışık girişler (örneğin aynı hesabın birkaç dakika arayla farklı kıtalardan giriş yapması).
Bu sinyalleri tek tek log satırı olarak bırakmak yerine, önceki bölümdeki "refresh_token_reuse" gibi olayları merkezi bir güvenlik event akışında toplamak, alarm eşiklerini (ör. aynı family'de art arda reuse) daha sonra ayarlamanı kolaylaştırır. Network Layer Optimization yazısında ele aldığım istemci tarafı 429 + Retry-After yönetimi, bu sinyallerin istemci ucunda nasıl göründüğünü anlamak için iyi bir başlangıç noktasıdır.
Log'da PII bırakmamak (KVKK/GDPR)
Güvenlik logları en çok ihmal edilen sızıntı noktalarından biridir: bir hata ayıklarken access token'ı, parolayı veya e-posta adresini olduğu gibi log'a yazmak, güvenlik önlemlerinin hepsini log dosyasının okunabilirliği kadar zayıf hale getirir. REST Security Cheat Sheet iki net kural verir: log injection saldırılarına karşı "sanitizing log data beforehand" (log verisini önceden temizle) ve "Consider logging token validation errors in order to detect attacks" — yani token doğrulama hatalarını logla ama token'ın kendisini değil.
Pratik kural: log satırına yazmadan önce kendine sor — bu alan bir kimlik bilgisi, token veya doğrudan kişisel veri mi? Öyleyse maskele (ör. token'ın yalnız son 4 karakteri) veya hiç yazma. KVKK ve GDPR'ın ortak beklentisi burada aynı noktaya çıkar: log'lar da kişisel veri işleme kapsamındadır ve saklama süresi, erişim kısıtı gerektirir.
Bu ilke özellikle hata izleme (error tracking) entegrasyonlarında unutuluyor: bir exception fırlatıldığında request body'nin tamamını olduğu gibi yakalayan bir handler, login endpoint'inde parolayı da yakalar. Hata izleme aracına giden payload'ı log'a giden payload'dan ayrı düşünmemek gerekir — ikisi de aynı redaksiyon kurallarına tabi olmalı, aksi halde "sadece production log'unu temizledim" demek yanıltıcı bir güvenlik hissi verir.
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
Bu rehberi bir üretim ortamına taşımadan önce kontrol etmen gereken maddeleri tek bir listede topladım; her satır makalede işlediğimiz bir bölüme karşılık geliyor ve atlarsan geriye dönüp o bölümü tekrar okumanı öneririm.
SSS
Mobil uygulama API'sinde rate limiting nasıl kurulur?
En sağlam yaklaşım tek katman değil üç katmandır: IP bazlı genel koruma, kullanıcı/token bazlı hesap koruması ve login/parola sıfırlama gibi hassas uçlara özel daha sıkı bir katman. nginx JWT claim'lerini okuyamadığı için IP ve login katmanları nginx'te ayrı limit_req_zone tanımlarıyla kurulur; kullanıcı bazlı katman doğrulanmış userId ile uygulama katmanında uygulanır. Limiti aşan istek OWASP'ın belirttiği gibi 429 Too Many Requests ile yanıtlanmalı.
Refresh token rotation nasıl uygulanır?
Her refresh çağrısında eski refresh token geçersiz kılınır ve yerine yeni bir token verilir (RFC 9700). Yeni token'lar ortak bir "family" kimliğiyle ilişkilendirilir; aynı family'den bir token ikinci kez kullanılmaya çalışılırsa bu reuse sayılır ve o family'deki tüm token'lar iptal edilir.
API anahtarını mobil uygulamada saklamak neden güvenli değil?
Çünkü uygulama ikilisi kullanıcının cihazındadır ve decompile edilebilir; obfuscation bunu geciktirir ama engellemez. Bunun yerine üçüncü parti API anahtarını kendi backend'inde tutup mobil app'in senin API'ne, senin API'nin de üçüncü partiye konuştuğu bir proxy deseni kurman gerekir.
Brute-force ve abuse'ü sunucuda nasıl tespit ederim?
Tek bir metrik yetmez: art arda başarısız girişler, refresh token reuse alarmları, bir endpoint'e normalin üzerinde istek hacmi ve tutarsız coğrafi giriş desenleri birlikte izlenmeli. Bu sinyalleri ayrı log satırları yerine merkezi bir güvenlik event akışında toplamak, eşikleri sonradan kalibre etmeyi kolaylaştırır.
IP bazlı rate limiting neden tek başına yetmez?
Çünkü mobil operatör NAT'ı arkasında yüzlerce meşru kullanıcı aynı çıkış IP'sini paylaşabilir; yalnız IP'ye dayanan bir kural ya onları toptan cezalandırır ya da saldırganın IP'sini sık değiştirmesiyle kolayca atlatılır. Kullanıcı/token bazlı ve endpoint bazlı katmanlarla birlikte çalışması gerekir.
JWT mi opak token mı kullanmalıyım?
Hangisini seçersen seç, imza/MAC doğrulamasını atlama — OWASP'ın REST Security Cheat Sheet'i imzasız JWT'lere asla izin verilmemesi gerektiğini net şekilde söyler. Opak token'lar sunucuda bir kayda bakmayı gerektirir ama iptal etmesi daha basittir; JWT'ler stateless'tır ama iptal için ayrı bir denylist (jti claim) mekanizması ister.
Sonuç
Mobil backend API güvenliği tek bir önlemle çözülmez; katmanların birbirini tamamlaması gerekir. IP + kullanıcı + endpoint bazlı rate limiting, kısa ömürlü access token ve rotasyonlu refresh token, reuse tespitiyle çalınmış token'ları anında geçersiz kılan bir mekanizma, hesap bazlı brute-force koruması ve istemciden çıkarılmış secret'lar — bunların hepsi birlikte çalıştığında gerçek bir savunma hattı oluşur.
Bu yapıyı kurarken Network Layer Optimization yazısındaki istemci tarafı network katmanı prensiplerinden, Swift Vapor ile Backend API yazısındaki backend kurulumundan ve iOS CI/CD Pipeline yazısındaki deploy disiplininden faydalanabilirsin. Hiçbiri tek başına yeterli değil; birlikte, mobil istemciyi "güvenilmez ama yönetilebilir" bir taraf olarak ele alan bütünsel bir yaklaşım oluşturuyorlar.
Kaynaklar
- OWASP REST Security Cheat Sheet — rate limiting, HTTP metod allowlist, JWT ve input validation için pratik kurallar.
- RFC 9700 — OAuth 2.0 Security Best Current Practice — refresh token rotation, sender-constraining ve PKCE gereksinimleri.
- OWASP API Security Top 10 2023 — Genel Bakış — 10 risk kategorisinin tam listesi.
- OWASP API4:2023 — Unrestricted Resource Consumption — rate limiting'in endpoint bazında ayarlanması gerektiğine dair kaynak.
- OWASP API2:2023 — Broken Authentication — credential stuffing ve kimlik doğrulama zafiyetleri.

