Skip to main content
Web Uygulama Güvenliği · 50 dk okuma

JWT Kullanan Uygulamalarda Kritik Yapılandırma Hataları · JWT Güvenliği

JWT kullanan bir uygulamada signature doğrulamak yalnızca ilk adımdır. Güvenli validator, kabul ettiği issuer, audience, algorithm, key, token type ve claim kurallarını açık biçimde tanımlar. Aksi hâlde cryptographically geçerli bir token yanlış API, yanlış tenant veya yanlış işlem bağlamında kabul edilebilir.

SX

SECNODEX Güvenlik Ekibi

Ofansif Güvenlik

Bir API aşağıdaki bearer token’ı alıyor:

eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6IjIwMjYtMDcifQ
.
eyJpc3MiOiJodHRwczovL2lkLmV4YW1wbGUuY29tIiwiYXVkIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20iLCJzdWIiOiI4NzQyMSIsInNjb3BlIjoib3JkZXJzOnJlYWQiLCJleHAiOjE3ODU1MDEwMDB9
.
Q7w...signature

Signature doğru. Token süresi dolmamış. Kullanıcı da gerçekten var.

Yine de bu token’ın kabul edilmesi güvenli olmayabilir.

Token başka bir authorization server tarafından üretilmiş olabilir. aud claim’i bu API yerine başka bir service’i hedefliyor olabilir. Token bir access token değil, login sırasında client’a verilen ID token olabilir. kid değeri uygulamayı yanlış key’e yönlendiriyor olabilir. İmza doğrulansa bile scope yalnızca okuma yetkisi verdiği hâlde endpoint para iadesi yapıyor olabilir. Kullanıcıya ait role token üretildikten sonra kaldırılmış, fakat üç gün geçerli token eski yetkiyi taşımaya devam ediyor olabilir.

JWT güvenliğindeki kritik gerçek budur:

Not

Geçerli signature, geçerli güvenlik kararı anlamına gelmez. JWT validator yalnız cryptographic integrity’yi değil token’ın kimden, kimin için, hangi amaçla, hangi key ile ve hangi zaman aralığında üretildiğini de doğrulamalıdır.

Bu yazıda yalnızca alg:none ve zayıf secret gibi bilinen problemleri sıralamayacağız. Algorithm allowlist, asymmetric-symmetric confusion, issuer ve audience binding, typ ile cross-JWT confusion, kid, jku ve JWKS trust, key rotation, stale authorization claim’leri, access token ile ID token farkı, refresh token rotation, logout ve revocation, browser storage, cookie kullanımı ve microservice validation sınırlarını teknik örneklerle inceleyeceğiz.

Amaç JWT’yi kötü veya güvensiz bir teknoloji ilan etmek değildir. Amaç, JWT kullanan sistemlerde cryptographically doğru görünen fakat güven modelini bozan yapılandırmaları görünür kılmaktır.

1. JWT nedir, ne değildir?

RFC 7519, JWT’yi iki taraf arasında claim taşımaya yarayan compact ve URL-safe bir format olarak tanımlar. JWT Claims Set bir JSON object’tir. Bu object bir JWS yapısının payload’u olabilir veya bir JWE yapısının plaintext’i olabilir.

Bu ayrım önemlidir:

  • JWS, signature veya MAC ile integrity ve authenticity sağlar
  • JWE, payload confidentiality sağlar
  • JWT, bu yapıların içinde taşınan claim formatıdır

Günlük kullanımda “JWT” denildiğinde çoğu ekip üç parçalı signed JWS Compact Serialization’ı kasteder:

base64url(header).base64url(payload).base64url(signature)

Örnek decoded header:

{
  "alg": "RS256",
  "typ": "at+jwt",
  "kid": "signing-key-2026-07"
}

Örnek decoded payload:

{
  "iss": "https://id.example.com",
  "sub": "user-87421",
  "aud": "https://orders-api.example.com",
  "scope": "orders:read",
  "iat": 1785497400,
  "exp": 1785498300,
  "jti": "53fb0df6-91a9-4bd6-9bc5-b9b58177d41f"
}

Base64url encryption değildir

Header ve payload çoğu signed JWT’de herkes tarafından decode edilebilir. Signature, payload’u gizlemez. Yalnızca doğru key’e sahip issuer’dan geldiğini ve imzalandıktan sonra değiştirilmediğini doğrulamaya yarar.

Bu yüzden signed token içine şu verileri koymak yanlış tasarımdır:

  • Password
  • Private key veya client secret
  • Tam kredi kartı verisi
  • Gereksiz sağlık verisi
  • Kimlik belgesi ayrıntıları
  • İç sistem credential’ları
  • Kullanıcıya gösterilmemesi gereken internal risk notları

JWE kullanılsa bile data minimization gereksinimi ortadan kalkmaz. Token farklı service, log, proxy ve diagnostic pipeline’lardan geçebilir. Hassas veri yalnız ihtiyaç duyulan alıcıya ve minimum claim setiyle taşınmalıdır.

2. JWT signature neyi kanıtlar, neyi kanıtlamaz?

Doğru biçimde doğrulanan signature şu iki iddiaya yardımcı olur:

  1. 1Token, validation key ile ilişkili signing key’e sahip tarafça üretilmiştir
  2. 2Signed header ve payload imzalandıktan sonra değiştirilmemiştir

Signature tek başına şunları kanıtlamaz:

  • Signing key’i kullanan issuer bu application için güvenilir mi?
  • Token bu API’ye mi üretildi?
  • Token access token mı, ID token mı, e-mail verification token’ı mı?
  • Kullanıcı hâlâ aktif mi?
  • Claim içindeki role hâlâ geçerli mi?
  • Kullanıcı istenen object’in sahibi mi?
  • Action business workflow içinde yapılabilir mi?
  • Token çalınmış mı?
  • Token’ın kullanıldığı cihaz veya client beklenen client mı?
  • İlgili request replay edilmiş mi?

Örneğin token’da şu claim bulunabilir:

{
  "sub": "user-87421",
  "role": "customer",
  "scope": "orders:read"
}

Bu token başarıyla doğrulandı diye kullanıcı şu resource’a erişemez:

GET /api/orders/ORD-99218 HTTP/1.1
Authorization: Bearer eyJ...

API, ORD-99218 ile sub=user-87421 arasındaki ownership veya delegated access ilişkisini ayrıca kontrol etmelidir. JWT authentication sağlar. Object-level authorization uygulamanın görevidir.

3. Güvenlik kontrolü token parser’da değil validator policy’dedir

JWT library genellikle token’ı parse eder, signature’ı doğrular ve claim’leri application’a verir. Hangi algorithm’in, issuer’ın, audience’ın, key’in ve token type’ın kabul edileceğine ise application karar verir.

Riskli yaklaşım şöyledir:

const decoded = decodeJwt(token)
req.user = decoded

Bu kod token’ı yalnız decode eder. Signature veya claim validation yapmaz.

Bir adım daha iyi görünen fakat yine eksik yaklaşım:

const payload = jwt.verify(token, publicKey)
req.user = payload

Signature library tarafından doğrulanıyor olabilir. Ancak kabul edilen algorithm, issuer, audience, required claim ve token type açıkça sınırlandırılmadıysa güven kararı eksik kalabilir.

Validator policy en az şu sözleşmeyi tanımlamalıdır:

token_profile: orders-api-access-token
accepted_issuers:
  - https://id.example.com
accepted_audiences:
  - https://orders-api.example.com
accepted_algorithms:
  - RS256
required_typ:
  - at+jwt
required_claims:
  - iss
  - sub
  - aud
  - exp
  - iat
maximum_token_age_seconds: 900
clock_skew_seconds: 60
key_source:
  issuer_bound_jwks: https://id.example.com/.well-known/jwks.json

Bu değerler örnektir. Token profile, uygulamanın protocol ve risk gereksinimine göre belirlenmelidir. Kritik nokta, policy’nin token’ın kendi söylediği değerlere göre dinamik biçimde genişlememesidir.

4. alg:none hâlâ test edilmeli mi?

alg:none, integrity protection içermeyen Unsecured JWS kullanımını ifade eder. Standart bunu belirli use case’ler için tanımlar. Fakat authentication veya API authorization token’ı olarak kabul edilmesi güvenli değildir.

Riskli token header’ı:

{
  "alg": "none",
  "typ": "JWT"
}

Modern ve güncel JWT library’lerinin çoğu Unsecured JWT’yi default olarak reddeder. Yine de şu nedenlerle test kapsamından tamamen çıkarılmamalıdır:

  • Eski library veya transitively gelen eski dependency
  • Signature validation’ı kapatan debug configuration
  • Token’ı gateway’de doğrulayıp downstream’de yalnız decode eden custom code
  • Test ortamından production’a taşınan permissive flag
  • Birden fazla validator’dan birinin farklı davranması
  • Custom parser’ın boş signature’ı valid sayması

Güvenli kontrol, yalnızca alg:none değerini yasaklamak değildir. Application’ın beklediği algorithm’i allowlist ile seçmesidir:

await jwtVerify(token, verificationKey, {
  algorithms: ["RS256"],
  issuer: "https://id.example.com",
  audience: "https://orders-api.example.com"
})

Bu configuration’da header alg=PS256, HS256 veya none dese bile application yalnız RS256 kabul eder.

5. Algorithm confusion nasıl ortaya çıkar?

JWT header içindeki alg, token’ın hangi cryptographic algorithm ile üretildiğini bildirir. Fakat bu değer saldırgan tarafından kontrol edilen token input’unun içindedir. Validator, “header ne diyorsa onu kullan” şeklinde karar vermemelidir.

En bilinen confusion senaryosu asymmetric ve symmetric algorithm ailelerinin karıştırılmasıdır:

  • Sistem normalde RS256 kullanır
  • Resource server’da RSA public key bulunur
  • Validator hem RS256 hem HS256 kabul eder
  • Eski veya yanlış yapılandırılmış library, HS256 görüldüğünde aynı public key byte’larını HMAC secret gibi kullanır
  • Public key zaten public olduğu için saldırgan kendi token’ına MAC üretebilir

Bu problem yalnız algorithm adının değiştirilmesinden oluşmaz. Root cause, key’in kullanım amacı ve algorithm ailesinin birbirine bağlanmamasıdır.

RFC 8725 şu yaklaşımı ister:

  • Caller desteklenen algorithm kümesini açıkça belirtmeli
  • Library bu kümenin dışındaki algorithm’i kullanmamalı
  • alg veya enc, gerçek cryptographic operation ile eşleşmeli
  • Her key tam olarak bir algorithm ile kullanılmalı

Riskli configuration

const claims = verify(token, key, {
  algorithms: ["HS256", "RS256"]
})

Daha güvenli configuration

const claims = verify(token, rsaPublicKey, {
  algorithms: ["RS256"],
  issuer: "https://id.example.com",
  audience: "https://orders-api.example.com"
})

Bir migration nedeniyle birden fazla algorithm gerçekten desteklenecekse key setleri ve validation path’leri ayrı tutulmalıdır. Token header’ının aynı key’i farklı algorithm ailesine taşımasına izin verilmemelidir.

6. Zayıf HMAC secret neden offline kırılabilir?

HS256, HMAC-SHA-256 kullanır. Signing ve validation aynı shared secret ile yapılır. Token ele geçirildiğinde saldırgan header, payload ve MAC’e sahiptir. Secret düşük entropy’ye sahipse aday secret’lar offline denenebilir. Rate limit bu saldırıyı durdurmaz, çünkü denemeler authorization server’a gönderilmeden yapılır.

Riskli secret örnekleri:

secret
my-jwt-key
company2026
SuperSecret123!

İnsan tarafından seçilmiş karmaşık görünen bir password, cryptographic key değildir. RFC 7518, HS256 için en az hash output büyüklüğünde, yani 256-bit key kullanılmasını ister.

Key CSPRNG ile üretilmeli ve secret manager’da tutulmalıdır:

openssl rand -base64 32

Bu command 32 random byte üretir. Çıktının environment variable, deployment manifest, CI log veya source repository içinde açığa çıkmaması gerekir.

Shared secret’ın blast radius’i

HMAC kullanan her doğrulayıcı signing secret’ı da bilmek zorundadır. On service aynı secret ile token doğruluyorsa bu service’lerden birinin compromise olması saldırgana token üretme imkânı verebilir.

Asymmetric modelde authorization server private key ile sign eder. Resource server’lar yalnız public key ile verify eder. Public key’in compromise olması yeni token üretme yetkisi vermez. Bu nedenle çok sayıda resource server bulunan mimarilerde asymmetric signing çoğu zaman daha uygun trust separation sağlar.

7. Asymmetric algorithm kullanmak tek başına yeterli mi?

Hayır. RS256, PS256, ES256 veya başka uygun asymmetric algorithm kullanılması shared-secret dağıtım riskini azaltabilir. Ancak şu hatalar devam edebilir:

  • Yanlış issuer’ın public key’i kabul ediliyor
  • aud doğrulanmıyor
  • Aynı key farklı token type’larında kullanılıyor
  • kid kontrolsüz key seçiyor
  • JWKS endpoint token tarafından belirleniyor
  • Eski key rotation sonrası gereğinden uzun süre kabul ediliyor
  • Private key source repository’de veya container image’da bulunuyor
  • Token ömrü aşırı uzun
  • Role claim’i stale kalıyor
  • API object-level authorization yapmıyor

Algorithm seçimi, validation policy’nin yalnızca bir parçasıdır.

8. Decode, parse ve verify aynı işlem değildir

JWT debugging araçları ve library method’ları token’ın header ve payload’unu signature doğrulamadan gösterebilir. Bu özellik gözlem için faydalıdır. Security decision için kullanılamaz.

Riskli middleware:

def jwt_middleware(request):
    token = request.headers["Authorization"].removeprefix("Bearer ")
    claims = jwt.decode(token, options={"verify_signature": False})
    request.user_id = claims["sub"]

Bu code saldırganın oluşturduğu herhangi bir payload’u kullanıcı kimliği yapar.

Doğru sıra şöyledir:

  1. 1Token boyutu ve serialization formatı sınırlandırılır
  2. 2JOSE header güvenilmeyen input olarak parse edilir
  3. 3Algorithm allowlist uygulanır
  4. 4Key, trusted issuer configuration üzerinden seçilir
  5. 5Signature veya MAC doğrulanır
  6. 6iss, aud, exp, nbf, iat ve required claim’ler doğrulanır
  7. 7Token type ve profile doğrulanır
  8. 8Claims yalnız bundan sonra authentication context’e dönüştürülür
  9. 9Endpoint ve resource authorization ayrıca uygulanır

Parse işlemi validation’dan önce teknik olarak gereklidir. Kritik olan, parse edilen değerlerin validation tamamlanmadan güvenilir kabul edilmemesidir.

Duplicate claim ve parser differential neden tehlikelidir?

JWT Claims Set bir JSON object’tir. Aynı claim adının bir payload içinde iki kez bulunması, farklı component’lerin aynı signed byte dizisini farklı yorumlamasına yol açabilir.

Örnek olarak aşağıdaki payload güvenli bir token profile’ında kabul edilmemelidir:

{
  "iss": "https://id.example.com",
  "aud": "https://profile-api.example.com",
  "aud": "https://orders-api.example.com",
  "sub": "user-87421",
  "exp": 1785501000
}

Bir JSON parser ilk aud değerini, başka bir parser son değeri kullanabilir. Gateway ilk değere göre karar verirken downstream service son değere göre authorization yaparsa signature her iki tarafta da geçerli olduğu hâlde trust decision ayrışır.

RFC 7519, JWT Claims Set içindeki claim adlarının unique olması gerektiğini belirtir. Uyumlu parser duplicate member içeren JWT’yi reddetmeli veya yalnız son değeri döndüren belirli JSON parser davranışını kullanmalıdır. Güvenlik açısından en sağlam yaklaşım duplicate JOSE header ve claim member’larını fail-closed reddetmektir. Özellikle gateway, service mesh ve application farklı language veya library kullanıyorsa bütün validation noktalarının aynı canonical profile üzerinde anlaşması gerekir.

Benzer risk JOSE header tarafında da oluşabilir:

{
  "alg": "RS256",
  "alg": "HS256",
  "kid": "signing-key-2026-07"
}

Validator ayrıca anlamadığı bir header parameter crit listesinde bulunuyorsa token’ı reddetmelidir. crit, alıcının token’ı doğru işleyebilmek için mutlaka anlaması gereken extension parameter’ları işaretler. Bilinmeyen crit değerini yok saymak, producer ile consumer arasında farklı signing input veya processing semantics oluşmasına neden olabilir.

JWT profile’ı şu kararları açıkça vermelidir:

  • Duplicate header member reddediliyor mu?
  • Duplicate claim member reddediliyor mu?
  • Unknown crit parameter fail-closed davranıyor mu?
  • Farklı gateway ve service library’leri aynı test fixture’ına aynı sonucu veriyor mu?
  • Token parse edildikten sonra yeniden serialize edilip başka component’e aktarılıyor mu?

Negative test suite yalnız malformed JSON test etmemeli, cryptographically valid fakat ambiguous JSON ve JOSE yapılarını da içermelidir.

9. iss doğrulanmazsa ne olur?

iss, token’ı üreten issuer’ı tanımlar. Resource server birden fazla identity provider’ın public key’ine erişebiliyor veya aynı platformda test ve production issuer’ları bulunuyorsa signature doğrulaması tek başına doğru trust domain’i göstermez.

Örnek risk:

Production issuer: https://id.example.com
Test issuer:       https://id-test.example.net

Test issuer’ın key’leri production API tarafından kabul ediliyor ve iss kontrol edilmiyorsa test ortamında token üretebilen biri production’a erişebilir.

Güvenli validation şunları gerektirir:

  • Beklenen issuer exact value olarak tanımlanır
  • iss case-sensitive karşılaştırılır
  • Issuer ile JWKS kaynağı önceden bağlanır
  • Test, development ve production trust domain’leri ayrılır
  • Multi-issuer gerekiyorsa her issuer için ayrı configuration ve policy bulunur
  • Token’daki iss değeri arbitrary discovery URL olarak kullanılmaz

Trailing slash farkı bile issuer identifier’ın parçası olabilir:

https://id.example.com
https://id.example.com/

Framework’ün normalize edeceği varsayılmamalıdır. Authorization server metadata’sındaki canonical issuer değeri kullanılmalıdır.

10. aud doğrulanmazsa token başka API’de kullanılabilir

aud, token’ın intended recipient’ını belirtir. Aynı authorization server billing, profile, order ve admin API’leri için token üretiyorsa her resource server kendi audience değerini doğrulamalıdır.

Şu token profile API için üretilmiş olabilir:

{
  "iss": "https://id.example.com",
  "aud": "https://profile-api.example.com",
  "scope": "profile:read",
  "sub": "user-87421"
}

Orders API yalnız signature ve issuer kontrol ederse token’ı kabul edebilir. Böylece intended audience dışındaki token substitution mümkün olur.

Doğru kontrol:

await jwtVerify(token, JWKS, {
  issuer: "https://id.example.com",
  audience: "https://orders-api.example.com",
  algorithms: ["RS256"]
})

aud ile client_id aynı şey değildir

OAuth modelinde:

  • aud, token’ın kullanılacağı resource server’ı tanımlar
  • client_id, token’ı talep eden OAuth client’ı tanımlar
  • sub, token’ın subject’ini tanımlar

Bu değerlerin anlamını birbirinin yerine kullanmak actor confusion üretebilir. Bir API hem belirli client’tan gelen token’ı hem de belirli resource audience’ını gerektiriyorsa ikisini ayrı doğrulamalıdır.

11. exp, nbf ve iat hangi kurallarla doğrulanmalı?

Time-based claim’ler birbirinin yerine geçmez:

ClaimAnlamıGüvenlikteki görevi
expExpiration TimeBu andan itibaren token kabul edilmez
nbfNot BeforeBu andan önce token kabul edilmez
iatIssued AtToken’ın ne zaman üretildiğini gösterir
auth_timeKullanıcının authentication zamanıHassas action için authentication freshness ölçümüne yardım eder
jtiJWT IDToken instance’ını tanımlar, replay ve revocation use case’lerinde kullanılabilir

exp bulunması ile güvenli token ömrü aynı şey değildir

Bir access token’ın exp değeri bir yıl sonrasını gösteriyorsa teknik olarak expiration vardır. Güvenlik bakımından ise çalınmış bearer token’ın kullanılabileceği pencere çok geniştir.

Token ömrü şu faktörlere göre belirlenmelidir:

  • Token access token mı, ID token mı, verification token mı?
  • Hangi data ve action’lara erişiyor?
  • Sender-constrained mı, bearer mı?
  • Resource server revocation kontrolü yapıyor mu?
  • User risk ve device posture değişikliği nasıl yansıyor?
  • Refresh mekanizması var mı?
  • İş sürekliliği ve offline çalışma gereksinimi nedir?

Sabit bir “her JWT 15 dakika olmalı” kuralı yoktur. Access token’ı kısa ömürlü tutmak çalınma etkisini azaltır. Fakat çok kısa ömür tek başına refresh token’ın güvensizliğini çözmez.

Clock skew sınırsız olmamalıdır

Distributed sistemlerde birkaç saniyelik zaman farkı normaldir. Bu yüzden validator küçük bir clock skew tanıyabilir. Clock skew 24 saat gibi geniş ayarlanırsa expiration kontrolü fiilen zayıflar.

ClockSkew = TimeSpan.FromSeconds(60)

Saat senkronizasyonu infrastructure seviyesinde yönetilmeli, security policy’yi gevşeterek telafi edilmemelidir.

iat future token ve aşırı yaş kontrolü için kullanılabilir

iat, RFC 7519 kapsamında token age hesaplamaya yardımcı olur. Application profile, token’ın kabul edilebilir maksimum yaşını ayrıca belirleyebilir:

const now = Math.floor(Date.now() / 1000)

if (payload.iat > now + 60) {
  throw new Error("Token issued in the future")
}

if (now - payload.iat > 15 * 60) {
  throw new Error("Token is older than the accepted profile")
}

Bu custom kontrol library’nin exp ve nbf validation’ının yerine geçmez. Token profile’a ek bir age bound getirir.

12. ID token ile access token neden karıştırılmamalı?

OpenID Connect login flow’unda client çoğu zaman hem ID token hem access token alır. İkisi JWT formatında olabilir. Aynı issuer tarafından sign edilebilir. Yine de amaçları farklıdır.

ÖzellikID tokenAccess token
Hedef alıcıOIDC clientResource server/API
Ana amaçAuthentication sonucunu client’a bildirmekAPI’ye delegated access taşımak
audGenellikle client IDResource server identifier
Kullanıldığı yerClient session oluşturmaAuthorization: Bearer ile API çağrısı
nonceİlgili OIDC flow’da doğrulanırTipik API access token claim’i değildir
API authorizationAPI bunu access token yerine kabul etmemeliScope ve resource policy ile kullanılır

API’nin ID token kabul etmesi token substitution problemidir. Kullanıcı geçerli biçimde login olmuş olabilir. Fakat ID token, bu API’ye hangi scope ile erişebileceğini göstermek üzere üretilmemiştir.

RFC 9068 profilini kullanan JWT access token header’ı explicit type taşır:

{
  "typ": "at+jwt",
  "alg": "RS256",
  "kid": "signing-key-2026-07"
}

Resource server bu profile uyuyorsa typ değerini, issuer’ı, audience’ı, signature’ı ve gerekli claim’leri birlikte doğrular.

13. Cross-JWT confusion nedir?

Aynı organization JWT formatını farklı amaçlarda kullanabilir:

  • OAuth access token
  • OpenID Connect ID token
  • Password reset token
  • E-mail verification token
  • Invitation token
  • Device enrollment token
  • Service-to-service token
  • Signed business document

Bu token’lar aynı key ile sign edilir ve validator yalnız signature’a bakarsa bir context için üretilmiş JWT başka context’te kabul edilebilir.

Örnek olarak password reset token’ı şu claim’leri taşıyor olsun:

{
  "sub": "user-87421",
  "purpose": "password-reset",
  "exp": 1785498000
}

API middleware yalnız sub ve signature kontrol ediyorsa bu token’ı authenticated access token gibi yorumlayabilir.

RFC 8725, farklı JWT türleri için mutually exclusive validation rules önerir. Ayrım şu kontrollerin kombinasyonuyla yapılabilir:

  • Ayrı typ değerleri
  • Ayrı issuer
  • Ayrı audience
  • Ayrı key
  • Ayrı required claim seti
  • Ayrı header gereksinimleri
  • Ayrı validation code path’i

Örnek profile’lar:

access_token:
  typ: at+jwt
  aud: https://orders-api.example.com
  required_claims: [iss, sub, aud, exp, iat, scope, client_id]

password_reset:
  typ: pwd-reset+jwt
  aud: https://account.example.com/password-reset
  required_claims: [iss, sub, aud, exp, iat, jti, purpose]
  required_values:
    purpose: password-reset

Mümkünse token türleri farklı key’lerle sign edilmelidir. Böylece yanlış validator path’i cryptographic olarak da başarısız olur.

14. typ kontrolü neden tek başına yeterli değildir?

typ, token’ın intended type’ını belirtir. Ancak header token’ı üreten tarafça belirlenir. Saldırgan da kendi geçersiz token’ında bu değeri yazabilir. Bu nedenle typ, signature ve diğer profile kurallarıyla birlikte doğrulanır.

Şu token yalnız typ doğru diye kabul edilmez:

{
  "alg": "none",
  "typ": "at+jwt"
}

Benzer biçimde signature geçerli olsa bile yanlış audience taşıyan token reddedilmelidir:

{
  "iss": "https://id.example.com",
  "aud": "mobile-client-id",
  "typ": "at+jwt"
}

Güvenli validation bir conjunction’dır:

signature valid
AND algorithm allowlisted
AND issuer trusted
AND audience expected
AND token type expected
AND lifetime valid
AND required claims present
AND application authorization satisfied

Kontrollerden biri başarısızsa token reddedilir.

15. kid neden güvenilmeyen input olarak görülmelidir?

kid, validator’ın hangi key ile signature doğrulayacağını seçmesine yardım eden key identifier’dır. Değer JWT header içindedir ve validation tamamlanmadan önce okunur. Bu nedenle güvenilmeyen input’tur.

Riskli custom key resolver:

function resolveKey(header) {
  return fs.readFileSync(`/app/keys/${header.kid}.pem`)
}

Bu tasarımda kid filesystem path’e doğrudan eklenir. Path traversal, unexpected file read veya application’ın yanlış byte’ları verification key olarak kullanması gündeme gelebilir.

Başka bir riskli örnek:

SELECT key_value
FROM signing_keys
WHERE key_id = '${kid}'

Burada JWT signature’dan önce SQL injection oluşabilir. Prepared statement gerekli olsa da asıl tasarım, kid değerini bounded identifier olarak ele almaktır.

Daha güvenli key selection

const allowedKeys = new Map([
  ["2026-07-primary", rsaPublicKey202607],
  ["2026-06-rotation", rsaPublicKey202606]
])

function resolveKey(protectedHeader) {
  if (protectedHeader.alg !== "RS256") {
    throw new Error("Unexpected algorithm")
  }

  if (typeof protectedHeader.kid !== "string") {
    throw new Error("Missing key id")
  }

  const key = allowedKeys.get(protectedHeader.kid)
  if (!key) {
    throw new Error("Unknown key id")
  }

  return key
}

JWKS kullanıldığında library key selection ve cache davranışını yönetebilir. Yine de issuer ve JWKS endpoint’i trusted configuration’dan gelmelidir.

16. jku, x5u ve embedded jwk nasıl trust boundary bozar?

JOSE header remote key URL veya embedded key taşıyabilir:

  • jku, JWK Set URL
  • x5u, X.509 certificate veya chain URL
  • jwk, doğrudan JSON Web Key
  • x5c, certificate chain

Validator bu değerleri token’dan okuyup koşulsuz güvenirse saldırgan kendi key’ini doğrulama sürecine sokabilir. Remote URL fetch işlemi ayrıca SSRF yüzeyi oluşturabilir.

Riskli mantık:

const header = decodeProtectedHeader(token)
const jwks = createRemoteJWKSet(new URL(header.jku))
return jwtVerify(token, jwks)

Saldırgan jku değerini kendi domain’ine yönlendirir, kendi public key’ini yayımlar ve kendi private key’iyle token sign eder. Validator trust kaynağını token’a teslim ettiği için signature doğru görünür.

Daha güvenli tasarım:

const ISSUER = "https://id.example.com"
const JWKS_URL = new URL(
  "https://id.example.com/.well-known/jwks.json"
)

const JWKS = createRemoteJWKSet(JWKS_URL)

await jwtVerify(token, JWKS, {
  issuer: ISSUER,
  audience: "https://orders-api.example.com",
  algorithms: ["RS256"],
  typ: "at+jwt"
})

Remote key fetch gerekiyorsa şu kurallar uygulanır:

  • URL token’dan değil trusted issuer metadata’sından gelir
  • HTTPS zorunludur
  • Host ve redirect policy sınırlandırılır
  • Private, loopback ve link-local destination’a yönelme engellenir
  • Response boyutu ve content type sınırlandırılır
  • Timeout ve cache uygulanır
  • Issuer ile JWKS kaynağı bağlanır
  • Fetch failure fail-open davranışına dönüşmez

SSRF savunmasının yalnız allowlist’ten ibaret olmamasının nedenlerini SSRF’yi Kapatmak İçin Yalnızca Allowlist Yeterli mi? yazısında ayrıntılı inceliyoruz.

17. JWKS endpoint’i nasıl güvenilir biçimde kullanılmalı?

JWKS, issuer’ın public verification key’lerini JWK Set formatında yayımlar. Resource server key rotation’ı takip etmek için bu endpoint’i kullanabilir.

Örnek JWK:

{
  "kty": "RSA",
  "use": "sig",
  "alg": "RS256",
  "kid": "2026-07-primary",
  "n": "uQ0...modulus",
  "e": "AQAB"
}

Güvenlik kontrolü yalnız kid eşleştirmek değildir:

  • kty beklenen key type ile eşleşmeli
  • use signing amacıyla uyumlu olmalı
  • alg application allowlist’iyle uyumlu olmalı
  • Key boyutu uygun olmalı
  • Key yalnız bağlı issuer için kullanılmalı
  • Aynı kid farklı issuer’lar arasında global key gibi yorumlanmamalı
  • Cache, rotation ve stale-key davranışı tanımlanmalı
  • Unknown kid kontrollü metadata refresh tetiklemeli
  • Refresh request’i saldırganın sınırsız outbound traffic üretmesine dönüşmemeli

Unknown kid ile cache miss flood

Her bilinmeyen kid için JWKS endpoint’ine yeni request atılması, saldırganın random kid değerleriyle authorization server’a yük bindirmesine neden olabilir.

Savunma yaklaşımı:

  • JWKS response cache
  • Minimum refresh interval
  • Negative cache veya unknown-key throttling
  • Bounded key set
  • Request timeout
  • Stale-while-revalidate stratejisi
  • Monitoring ve alert

Cache uzunluğu key rotation süreciyle uyumlu olmalıdır. Çok kısa cache availability yükü yaratır. Çok uzun cache yeni key’in kabulünü geciktirebilir veya revoke edilen key’i gereğinden uzun yaşatabilir.

18. Key rotation neden yalnız yeni key üretmek değildir?

Signing key rotation üç zaman penceresini yönetir:

  1. 1Yeni key’in yayınlanması
  2. 2Yeni key ile token üretilmeye başlanması
  3. 3Eski key ile üretilmiş token’ların doğal olarak sona ermesi

Güvenli rollout örneği:

T0: New public key JWKS'e eklenir
T1: Cache propagation beklenir
T2: Authorization server yeni private key ile sign etmeye başlar
T3: Eski key, eski token'ların maksimum ömrü boyunca JWKS'te tutulur
T4: Eski token'lar sona erdikten sonra eski public key kaldırılır

Private key compromise durumunda doğal expiration beklenmeyebilir. Emergency rotation, affected token revocation ve incident response gerekir.

Key lifecycle şu sorularla tasarlanmalıdır:

  • Private key nerede üretiliyor?
  • HSM veya managed key service kullanılıyor mu?
  • Hangi service sign yetkisine sahip?
  • Key export edilebilir mi?
  • Rotation periyodu nedir?
  • kid benzersiz mi?
  • Eski key ne kadar süre validate edilecek?
  • Compromise durumunda hızlı revoke mümkün mü?
  • JWKS cache ne kadar sürede refresh oluyor?
  • Audit log signing operation’larını izliyor mu?
  • Backup ve disaster recovery key materyalini nasıl koruyor?

Private key’in source code repository, container image veya .env dosyası içinde tutulması algorithm’in gücünü anlamsızlaştırabilir.

19. JWT claim’leri authorization’ın tamamı değildir

Token’daki roles, groups, entitlements ve scope claim’leri authorization kararına girdi sağlar. Ancak her karar yalnız claim ile verilemez.

Örnek token:

{
  "sub": "user-87421",
  "roles": ["support-agent"],
  "scope": "tickets:read tickets:update",
  "tenant_id": "tenant-a"
}

Bu token şu sorulara tek başına cevap vermez:

  • İstenen ticket tenant-a içinde mi?
  • Support agent bu region’a atanmış mı?
  • Ticket restricted customer’a mı ait?
  • Update edilen field support rolü için izinli mi?
  • Kullanıcının erişimi token üretildikten sonra kaldırıldı mı?
  • Action için approval gerekiyor mu?

Endpoint authorization şu katmanları birleştirebilir:

valid token
AND required scope
AND allowed role
AND tenant match
AND object relationship
AND field-level policy
AND current business state
AND risk-based step-up

JWT, authorization verisini taşır. Policy enforcement yine resource server’ın sorumluluğundadır.

20. Stale role ve entitlement problemi nasıl yönetilir?

Self-contained JWT’nin avantajı, resource server’ın her request’te authorization server’a gitmeden token’ı doğrulayabilmesidir. Aynı özellik revocation ve değişen yetkilerin yayılması için gecikme yaratır.

Bir kullanıcı finance-admin rolünden çıkarıldığında elindeki access token bir saat daha geçerliyse claim de bir saat boyunca eski kalabilir.

Risk bazlı seçenekler:

  • Access token ömrünü kısa tutmak
  • Kritik action öncesi current entitlement kontrolü yapmak
  • Central policy decision point kullanmak
  • Token version veya session version doğrulamak
  • Riskli role değişiminde session ve refresh token family’yi revoke etmek
  • Opaque token ve introspection tercih etmek
  • Kritik endpoint için step-up authentication istemek
  • Security event ile cache invalidation uygulamak

Her request’te database sorgulamak JWT’nin stateless avantajını azaltabilir. Bu her zaman kötü değildir. Kritik authorization’ın freshness gereksinimi performans tercihlerinden önce gelir.

21. Scope, role ve permission mapping hataları

Bir token cryptographically doğru olsa bile framework claim’leri yanlış authority’ye map edebilir.

Yaygın hatalar şunlardır:

  • scope string’ini tek permission olarak yorumlamak
  • scp, scope, roles ve custom claim’leri karıştırmak
  • Claim separator farklarını görmezden gelmek
  • Case normalization ile farklı permission’ları birleştirmek
  • admin=true gibi client-specific claim’i global admin yapmak
  • Issuer’a göre değişen role vocabulary’sini ortak kabul etmek
  • Group membership’i doğrudan application permission’a çevirmek
  • Namespace collision nedeniyle başka issuer’ın claim’ini kabul etmek

Örneğin RFC 9068 profile’ında scope space-delimited string olabilir:

{
  "scope": "orders:read orders:update"
}

Doğru parse:

function parseScopes(payload) {
  if (typeof payload.scope !== "string") {
    return new Set()
  }

  return new Set(
    payload.scope.split(" ").filter(Boolean)
  )
}

const scopes = parseScopes(payload)

if (!scopes.has("orders:update")) {
  throw new ForbiddenError()
}

Substring araması kullanılmamalıdır:

// Riskli
if (payload.scope.includes("admin")) {
  grantAdminAccess()
}

not-admin, admin-readonly veya başka bir scope yanlış eşleşebilir. Permission exact value olarak değerlendirilmelidir.

Role, tenant boundary’yi kaldırmaz

role=manager claim’i kullanıcının bütün tenant’lardaki record’lara erişebileceği anlamına gelmez. Policy resource context’i içermelidir:

public Order loadOrder(
        JwtAuthenticationToken authentication,
        UUID orderId) {

    String tenantId = authentication.getToken()
        .getClaimAsString("tenant_id");

    return orderRepository
        .findByIdAndTenantId(orderId, tenantId)
        .orElseThrow(NotFoundException::new);
}

Bu query tek başına bütün authorization’ı çözmez. User-order ilişkisi ve action policy de uygulanabilir. Fakat cross-tenant object’in application memory’sine getirilmesini önleyen güçlü bir başlangıçtır.

client_credentials token’ı user token olarak yorumlanmamalıdır

OAuth client_credentials grant’ında token bir end user adına değil, client application veya workload adına üretilir. Buna rağmen bazı authorization server’lar hem user-delegated token’larda hem machine-to-machine token’larda actor kimliğini sub claim’i içinde taşır.

İki token aynı issuer ve audience ile üretilebilir:

{
  "iss": "https://id.example.com",
  "aud": "https://orders-api.example.com",
  "sub": "user-87421",
  "client_id": "web-portal",
  "grant_type": "authorization_code",
  "scope": "orders:read"
}
{
  "iss": "https://id.example.com",
  "aud": "https://orders-api.example.com",
  "sub": "batch-client-42",
  "client_id": "batch-client-42",
  "grant_type": "client_credentials",
  "scope": "orders:read"
}

API yalnız sub değerini alıp user_id olarak yorumlarsa client identity ile resource owner identity aynı namespace içinde karışabilir. Self-service client registration bulunan sistemlerde risk daha da büyür. Client kendi client_id değerini bir user identifier’a benzetebiliyorsa user adına işlem yapıyormuş gibi değerlendirilebilir.

RFC 9700, resource server’ın resource owner ile client identity’sini birbirinden ayırabilmesi gerektiğini vurgular. Güvenli profile şu ayrımı cryptographically doğrulanmış claim’lerle yapmalıdır:

type Actor =
  | {
      kind: "user"
      subject: string
      clientId: string
    }
  | {
      kind: "workload"
      clientId: string
    }

function buildActor(payload: Record<string, unknown>): Actor {
  const grantType = payload.grant_type
  const subject = payload.sub
  const clientId = payload.client_id

  if (typeof clientId !== "string") {
    throw new Error("client_id is required")
  }

  if (grantType === "client_credentials") {
    return { kind: "workload", clientId }
  }

  if (grantType === "authorization_code" && typeof subject === "string") {
    return { kind: "user", subject, clientId }
  }

  throw new Error("unsupported token actor profile")
}

Bu fonksiyon yalnız token’ın signature, issuer, audience, type ve required claim validation’ı başarıyla tamamlandıktan sonra çalıştırılmalıdır. Unverified payload’dan actor üretmek, doğru modelin yanlış trust boundary içinde kullanılmasına neden olur.

Buradaki grant_type claim’i standart JWT access token profile’ının zorunlu claim’i değildir. Örnek, authorization server ile resource server arasında tanımlanmış private token profile’ını temsil eder. Gerçek sistem gty, idtyp, token_use, act, azp veya başka bir profile-specific claim kullanabilir. Önemli olan token’ın kendi isteğine göre rastgele claim kabul etmek değil, issuer ile önceden kararlaştırılmış ve mutually exclusive validation rules uygulamaktır.

Endpoint policy de actor türünü açıkça sınırlandırmalıdır:

GET  /api/me/orders      → yalnız user-delegated token
POST /api/jobs/reconcile → yalnız workload token
POST /api/orders         → user veya workload için farklı policy

User token ile workload token aynı endpoint’e kabul edilecekse ownership, delegation ve audit semantics ayrı tanımlanmalıdır. Log’da yalnız sub tutulması, olay müdahalesinde gerçek actor ile calling client’ın birbirinden ayrılmasını engeller.

22. Multi-tenant JWT validation neden daha zordur?

Multi-tenant SaaS veya birden fazla identity provider kabul eden API’de validator, token’a bakarak hangi tenant configuration’ını kullanacağını seçebilir. Buradaki bootstrap kararı güvenlik açısından kritiktir.

Riskli model:

const unverified = decodeJwt(token)
const tenant = await tenantRepository.findById(unverified.tenant_id)
const jwks = createRemoteJWKSet(new URL(tenant.jwksUrl))
return jwtVerify(token, jwks)

tenant_id doğrulanmadan kullanılıyor. Tenant onboarding zayıfsa veya tenant metadata değiştirilebiliyorsa arbitrary issuer ve JWKS trust oluşabilir.

Daha güvenli model şu özelliklere sahip olur:

  • Kabul edilen issuer’lar onboarding sırasında doğrulanır
  • Issuer canonical ve benzersiz tenant configuration’a bağlanır
  • JWKS URL trusted metadata’dan gelir
  • Dynamic registration sıkı authorization gerektirir
  • iss, tenant_id ve audience ilişkisi validation sonrası kontrol edilir
  • Cross-tenant key cache partition edilir
  • Aynı kid farklı issuer’lar arasında karışmaz
  • Tenant silme veya disable işlemi validation cache’ini etkiler

Subject de yalnız sub olarak global kimlik değildir. RFC 7519’a göre sub issuer context’inde locally unique olabilir. Bu nedenle user identity için çoğu sistemde güvenli composite key şudur:

(iss, sub)

Yalnız sub=user-123 ile global user eşleştirmek, iki issuer aynı subject değerini ürettiğinde account confusion oluşturabilir.

23. “Stateless JWT’de logout olmaz” doğru mu?

JWT’nin self-contained olması logout yapılamayacağı anlamına gelmez. Yalnız server-side session row’u silmek kadar anlık ve doğal olmayabilir.

Logout tasarımında üç ayrı credential düşünülmelidir:

  1. 1Access token
  2. 2Refresh token
  3. 3Browser veya application session

Client’taki token’ı silmek, saldırganın daha önce kopyaladığı access token’ı geçersiz kılmaz. Bu yüzden logout beklentisi açıkça tanımlanmalıdır.

Seçenekler:

  • Kısa ömürlü access token ve refresh token revocation
  • Critical session için jti denylist
  • User veya session bazlı token version
  • Introspection kullanan opaque token
  • Riskli olaylarda signing key rotation değil targeted revocation
  • Sender-constrained token
  • Gateway’de central revocation cache

Her logout’ta signing key rotate edilmez

Signing key bütün kullanıcıları etkiler. Tek kullanıcının logout veya password reset işlemi için global key rotation yapmak availability ve operasyon riski yaratır.

Targeted state örneği:

{
  "sub": "user-87421",
  "sid": "session-a816",
  "token_version": 7,
  "exp": 1785498300
}

Kritik endpoint veya gateway current session version’ı kontrol edebilir. Bu yaklaşım state ekler. Fakat hızlı revocation gereksinimini karşılayabilir.

24. Password değişikliği ve account disable token’ları etkilemeli mi?

Kullanıcı password değiştirdiğinde mevcut token’ların ne olacağı risk kararına bağlıdır. Account compromise ihtimali varsa yalnız yeni password kaydetmek yeterli değildir.

Değerlendirilmesi gereken olaylar:

  • Password reset
  • MFA ekleme, kaldırma veya recovery
  • E-mail ve telefon değişikliği
  • Role veya group değişikliği
  • Account suspension
  • Device revoke
  • Şüpheli login
  • Admin tarafından session sonlandırma

Riskli uygulama yalnız refresh token’ı client’tan siler. Server tarafındaki refresh token geçerli kalır ve saldırgan yeni access token üretmeye devam eder.

Sağlıklı lifecycle şu aksiyonları risk bazlı birleştirebilir:

  • İlgili refresh token family’lerini revoke etmek
  • Aktif session’ları sonlandırmak
  • Access token denylist veya token version güncellemek
  • Hassas endpoint’lerde re-authentication zorunlu kılmak
  • Kullanıcıya session ve device görünürlüğü vermek
  • Security event üretmek
  • Recovery channel değişikliğinde ek doğrulama istemek

JWT, session management’in alternatifi değildir. Token, session lifecycle’ın taşıyıcılarından biridir. Daha geniş lifecycle hatalarını Oturum Yönetiminde En Sık Yapılan Hatalar yazısında ele alıyoruz.

25. Refresh token’ı JWT yapmak zorunlu mu?

Hayır. Access token JWT olabilirken refresh token opaque random value olabilir. Refresh token’ın client tarafından okunabilir claim taşıması çoğu use case için gerekli değildir.

Opaque refresh token modeli:

raw token:  CSPRNG ile üretilen yüksek entropy'li secret
database:   token hash + family id + user + client + expiry + status
client:     yalnız raw token

Database’de raw token yerine hash saklanabilir:

const rawRefreshToken = randomBytes(32).toString("base64url")
const tokenHash = createHash("sha256")
  .update(rawRefreshToken)
  .digest("hex")

await refreshTokenRepository.insert({
  tokenHash,
  familyId,
  subjectId,
  clientId,
  status: "ACTIVE",
  expiresAt
})

Refresh request sırasında raw token hash’lenir ve constant-time karşılaştırma veya güvenli repository lookup ile doğrulanır.

Rotation ve reuse detection

RFC 9700, public client refresh token’larının sender-constrained olmasını veya refresh token rotation kullanmasını ister.

Rotation akışı:

RT1 kullanılır
  ↓
RT1 USED yapılır
  ↓
AT2 + RT2 üretilir
  ↓
RT1 tekrar görülürse token family compromise kabul edilir
  ↓
RT2 dahil family revoke edilir

Atomic transaction gerekir:

UPDATE refresh_tokens
SET status = 'USED', used_at = NOW()
WHERE token_hash = :token_hash
  AND status = 'ACTIVE'
  AND expires_at > NOW();

Affected row sayısı bir değilse request başarısız olmalıdır. Ardından yeni token aynı transaction veya güvenilir state machine içinde oluşturulur.

Parallel refresh, mobile retry ve birden fazla browser tab’i false reuse detection üretebilir. Grace window tasarlanıyorsa saldırgana replay penceresi açmayacak biçimde bounded, observable ve client instance’a bağlı olmalıdır.

26. Access token ne kadar yaşamalı?

Tek bir evrensel süre yoktur. Süreyi belirleyen risk bileşenleri şunlardır:

DeğişkenDaha kısa ömrü destekleyen durumDaha uzun ömrü gerekçelendirebilecek durum
Token tipiBearer access tokenSender-constrained ve dar scope
EtkiFinansal veya kişisel veriDüşük riskli read-only veri
RevocationAnlık revoke yokIntrospection veya güçlü session state var
ClientBrowser veya unmanaged deviceManaged workload identity
NetworkGeniş internet erişimiDar service mesh ve mTLS
ScopeGeniş admin scopeTek resource ve tek action
RefreshGüvenli rotation varOffline çalışma zorunluluğu

Access token ömrü kısaldıkça refresh endpoint kritik hâle gelir. Refresh token süresiz, rotation’sız ve cihazda kötü saklanıyorsa beş dakikalık access token sahte güven üretir.

Süre kararı ölçülmelidir:

  • Ortalama token yaşı
  • Expired token rejection oranı
  • Refresh reuse event’i
  • Revoke ile enforcement arasındaki süre
  • Critical action’da re-authentication oranı
  • Stolen token senaryosundaki maksimum exposure window

27. Bearer token çalınırsa signature saldırganı durdurur mu?

Hayır. Bearer token’a sahip olan taraf, token geçerli olduğu sürece onu kullanabilir. Saldırgan token’ı değiştirmek zorunda değildir. Geçerli token’ı replay eder.

RFC 6750, bearer token’ı elinde bulunduran tarafın cryptographic key possession göstermeden kullanabildiğini açıklar.

Token leakage kaynakları:

  • XSS
  • Browser extension
  • Mobile backup veya log
  • Reverse proxy access log
  • APM ve error monitoring
  • URL query parameter
  • Referrer ve browser history
  • CI/CD output
  • Support screenshot
  • Crash dump
  • Misconfigured CDN veya WAF log
  • Third-party SDK
  • Unencrypted local storage

Bu nedenle JWT signature, token theft’e karşı çözüm değildir. Çözüm token’ın storage ve transport protection’ı, kısa ömür, dar audience ve scope, revocation, detection ve uygun use case’te sender constraint’in birleşimidir.

28. DPoP ve mTLS hangi riski azaltır?

Sender-constrained token, token kullanımını belirli cryptographic key’e bağlar. Token çalınsa bile saldırgan ilgili private key olmadan resource server’a geçerli proof sunamaz.

İki yaygın mekanizma:

  • OAuth mTLS
  • DPoP

RFC 9449, DPoP ile client’ın private key possession gösterdiği application-level proof mekanizmasını tanımlar.

DPoP proof tipik olarak şunları bağlar:

  • HTTP method
  • Target URI
  • Issued time
  • Unique identifier
  • Access token hash

Ancak DPoP sihirli çözüm değildir:

  • Private key de token ile birlikte çalınırsa koruma zayıflar
  • Browser context’inde XSS legitimate client’ı proxy gibi kullanabilir
  • Replay cache ve jti kontrolü gerekir
  • URI canonicalization doğru olmalıdır
  • Authorization server ile resource server binding’i uyumlu olmalıdır
  • Key rotation ve device recovery tasarlanmalıdır

Sender constraint, BOLA veya business logic açığını çözmez. Geçerli kullanıcının başka kullanıcının object’ine erişmesini backend authorization engeller.

29. Browser’da JWT nerede saklanmalı?

Bu sorunun tek cümlelik cevabı yoktur. localStorage kullanmayın, cookie kullanın yaklaşımı risk modelini eksik bırakır.

localStorage ve sessionStorage

JavaScript erişebilir. XSS gerçekleşirse token okunabilir veya kullanıcının browser context’inden API request’i gönderilebilir. sessionStorage persistence’ı azaltır, fakat XSS erişimini ortadan kaldırmaz.

HttpOnly cookie

JavaScript cookie değerini doğrudan okuyamaz. Bu token exfiltration yüzeyini azaltır. Browser cookie’yi uygun request’lere otomatik gönderdiği için CSRF riski ayrıca yönetilmelidir.

Set-Cookie: __Host-session=<opaque-or-token>; Path=/; Secure; HttpOnly; SameSite=Lax

__Host- prefix kullanılıyorsa Secure, Path=/ gerekir ve Domain kullanılmaz. SameSite seçimi application flow’una göre yapılmalıdır. Cross-site use case SameSite=None gerektiriyorsa Secure zorunludur ve CSRF token veya origin validation gibi ek kontroller değerlendirilir.

In-memory storage

Page refresh sonrasında token kaybolabilir ve XSS, token’ı doğrudan okumasa bile active page context’inde action gerçekleştirebilir. Persistence azaltılır, XSS etkisi tamamen kaldırılmaz.

Backend for Frontend

BFF modelinde browser uzun ömürlü OAuth token’larını doğrudan yönetmez. Browser ile BFF arasında hardened session cookie kullanılır. BFF token’ları server-side tutar ve downstream API’ye ekler. Bu yaklaşım browser exposure’ı azaltabilir, fakat CSRF, session fixation, BFF authorization ve server-side token storage risklerini doğru yönetmek gerekir.

Storage kararı application architecture, XSS yüzeyi, cross-site requirement, offline çalışma, mobile/web client farkı ve threat model üzerinden verilmelidir.

30. Token neden URL’ye konulmamalı?

Access token veya session token’ın query parameter’da taşınması çok sayıda kopya üretir:

https://api.example.com/export?access_token=eyJ...

Token şu yerlere sızabilir:

  • Browser history
  • Reverse proxy log
  • Web server access log
  • Analytics
  • Referrer header
  • Screenshot ve support ticket
  • Bookmark
  • Monitoring dashboard
  • CDN log

Bearer access token için tercih edilen taşıma Authorization header’dır:

GET /api/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJ...

Header kullanımı da log güvenliği gerektirir. APM agent veya reverse proxy bütün header’ları kaydediyorsa token yine sızabilir. Log pipeline’da redaction uygulanmalıdır:

redact_headers:
  - authorization
  - cookie
  - set-cookie

redact_fields:
  - access_token
  - refresh_token
  - id_token

Tam token yerine investigation için gerektiğinde güvenli hash, jti, issuer, audience ve validation outcome loglanabilir. Log’un kendisi yeni credential repository’ye dönüşmemelidir.

31. API gateway doğruladıysa downstream service tekrar doğrulamalı mı?

Gateway’de token validation merkezi policy ve operasyon kolaylığı sağlar. Fakat downstream service’in gateway dışından erişilebildiği, internal network’ün koşulsuz trusted sayıldığı veya identity header’larının overwrite edilmediği mimarilerde tek kontrol noktası bypass edilebilir.

Riskli akış:

Internet → Gateway → Orders API
                       ↑
              Internal workload

Orders API X-User-Id ve X-Roles header’larını yalnız gateway’in eklediğini varsayıyor olabilir. Internal workload Orders API’ye doğrudan ulaşabiliyorsa kendi header’ını yazabilir.

Savunma seçenekleri:

  • Downstream service token’ı yeniden doğrular
  • Gateway ile service arasında mTLS workload identity kullanılır
  • Network policy doğrudan erişimi kapatır
  • Gateway gelen identity header’larını silip yeniden üretir
  • Signed internal token veya context envelope kullanılır
  • Service audience’ı gateway audience’ından ayrılır
  • Authorization service içinde resource context ile tekrar uygulanır

Her service aynı token’ı kabul etmeli mi?

Tek token’ı bütün microservice’lerde kabul etmek blast radius’i büyütür. Order API için üretilen token inventory veya payment service’te geçmemelidir.

Token exchange veya audience-specific token yaklaşımı kullanılabilir:

Client token
aud = api-gateway
scope = orders:create
        ↓ token exchange
Internal token
aud = orders-service
scope = order:write
act = client/user context

Her hop’ta identity propagation ile delegation anlamı birbirine karıştırılmamalıdır. Service, end user adına mı, kendi workload identity’siyle mi, yoksa ikisinin birleşimiyle mi hareket ettiğini açıkça bilmelidir.

32. Spring Security’de güvenli JWT validation örneği

Spring Security Resource Server, issuer metadata üzerinden JWKS keşfedebilir ve signature, iss, exp ve nbf kontrollerini yapabilir. Audience ayrıca tanımlanmalıdır.

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://id.example.com
          audiences: https://orders-api.example.com
          jws-algorithms: RS256

Bu configuration şu güven kararlarını görünür kılar:

  • Trusted issuer sabittir
  • Resource server kendi audience değerini bekler
  • Kabul edilen signing algorithm allowlist edilir
  • Key discovery issuer metadata üzerinden yapılır

Custom validator gerektiğinde default issuer ve timestamp validator’ları kaldırılmamalıdır. Yeni validator bunlarla compose edilmelidir:

@Bean
JwtDecoder jwtDecoder() {
    String issuer = "https://id.example.com";

    NimbusJwtDecoder decoder = NimbusJwtDecoder
        .withIssuerLocation(issuer)
        .jwsAlgorithm(SignatureAlgorithm.RS256)
        .build();

    OAuth2TokenValidator<Jwt> defaults =
        JwtValidators.createDefaultWithIssuer(issuer);

    OAuth2TokenValidator<Jwt> audience = new JwtClaimValidator<>(
        JwtClaimNames.AUD,
        aud -> aud != null &&
            aud.contains("https://orders-api.example.com")
    );

    decoder.setJwtValidator(
        new DelegatingOAuth2TokenValidator<>(defaults, audience)
    );

    return decoder;
}

Kod örneği kullanılan Spring Security version’ına göre import ve API uyarlaması gerektirebilir. Güvenlik ilkesi değişmez. Default validation korunur, audience ve application-specific profile buna eklenir.

Authority mapping’i ayrıca kontrol edin

Spring, scope claim’lerini varsayılan olarak SCOPE_ prefix’iyle authority’ye dönüştürebilir. Custom JwtAuthenticationConverter kullanılıyorsa yanlış claim veya prefix mapping’i privilege escalation doğurabilir.

@PreAuthorize("hasAuthority('SCOPE_orders:write')")
public Order updateOrder(UUID orderId, UpdateOrderRequest request) {
    return orderService.update(orderId, request);
}

Bu annotation object ownership’i yine çözmez. Method içindeki policy veya repository query actor-resource ilişkisini doğrulamalıdır.

Java Spring source code review başlangıç noktalarını Java Spring Projelerinde Güvenlik Kod İncelemesine Nereden Başlanır? yazısında bulabilirsiniz.

33. ASP.NET Core’da güvenli JWT validation örneği

ASP.NET Core JWT Bearer middleware, authority metadata ve configured audience üzerinden validation yapabilir.

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://id.example.com";
        options.Audience = "https://orders-api.example.com";
        options.RequireHttpsMetadata = true;
        options.SaveToken = false;

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            RequireSignedTokens = true,
            RequireExpirationTime = true,
            ValidAlgorithms = new[] { "RS256" },
            ClockSkew = TimeSpan.FromSeconds(60)
        };
    });

Microsoft’un CA5404 rule’u RequireExpirationTime, ValidateAudience, ValidateIssuer veya ValidateLifetime kontrollerinin kapatılmasını security risk olarak işaretler.

Riskli configuration:

options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuer = false,
    ValidateAudience = false,
    ValidateLifetime = false,
    RequireExpirationTime = false
};

Development ortamında sorunu hızlı çözmek için validation flag’lerini kapatmak, configuration production’a taşındığında kritik authentication bypass’a dönüşebilir.

Claim mapping sürprizlerini görünür kılın

Framework bazı claim adlarını platform-specific claim type’lara map edebilir. Authorization policy hangi raw claim veya mapped claim’i kullandığını açıkça tanımlamalıdır.

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("OrdersWrite", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("scope", "orders:write");
    });
});

Gerçek issuer scope claim’ini space-delimited tek string olarak veriyorsa RequireClaim kullanımının beklenen biçimde çalışıp çalışmadığı integration test ile doğrulanmalıdır. Claim shape varsayılmamalıdır.

.NET authorization hatalarının source code üzerinden nasıl izlendiğini .NET’te Yetkilendirme Hatalarını Kaynak Koddan Tespit Etme yazısında daha derin ele alıyoruz.

34. Node.js jose ile explicit validation örneği

Node.js tarafında trusted JWKS URL, issuer, audience, algorithm, type ve required claim’ler birlikte tanımlanabilir:

import {
  createRemoteJWKSet,
  jwtVerify
} from "jose"

const issuer = "https://id.example.com"
const audience = "https://orders-api.example.com"

const JWKS = createRemoteJWKSet(
  new URL("https://id.example.com/.well-known/jwks.json")
)

export async function verifyAccessToken(token: string) {
  const result = await jwtVerify(token, JWKS, {
    issuer,
    audience,
    algorithms: ["RS256"],
    typ: "at+jwt",
    requiredClaims: [
      "iss",
      "sub",
      "aud",
      "exp",
      "iat"
    ],
    clockTolerance: 60,
    maxTokenAge: "15m"
  })

  return {
    subject: result.payload.sub,
    scopes: typeof result.payload.scope === "string"
      ? new Set(result.payload.scope.split(" ").filter(Boolean))
      : new Set<string>(),
    tokenId: result.payload.jti
  }
}

Burada decodeJwt ile authorization context oluşturulmuyor. Remote JWKS URL header’dan alınmıyor. Expected values application configuration’dan geliyor.

Production code şu sınırları da ele almalıdır:

  • Authorization header formatı
  • Token maksimum boyutu
  • Error detail leakage
  • Unknown key refresh throttling
  • Request timeout
  • Observability
  • Scope ve claim schema validation
  • Revocation veya session state gereksinimi

35. JWT mi opaque token mı?

JWT her use case için zorunlu değildir. Token formatı architecture kararıdır.

KriterJWT access tokenOpaque access token
Local validationEvetGenellikle introspection gerekir
Network dependencyHer request’te gerekli olmayabilirIntrospection cache yoksa daha yüksek
Revocation freshnessEk state veya kısa ömür gerekirAuthorization server state’i daha hızlı yansıtabilir
Claim exposureSigned JWT payload okunabilirClient token içeriğini yorumlayamaz
Key managementSigning ve JWKS lifecycle gerekirIntrospection credential ve availability gerekir
Microservice scalingLocal validation avantajlı olabilirCentral policy daha tutarlı olabilir
Token boyutuClaim’lerle büyüyebilirGenellikle daha compact
PrivacyData minimization kritikSensitive state server-side tutulabilir

Şu durumlarda opaque token mantıklı olabilir:

  • Anlık revocation önemliyse
  • Authorization state sık değişiyorsa
  • Resource server sayısı sınırlıysa
  • Claim’lerin client tarafından görülmesi istenmiyorsa
  • Central introspection availability sağlanabiliyorsa

JWT şu durumlarda avantaj sağlayabilir:

  • Çok sayıda resource server local validation yapacaksa
  • Authorization server’a her request’te dependency istenmiyorsa
  • Claim seti dar ve stabilse
  • Key rotation ve validation policy olgun biçimde yönetilebiliyorsa

JWT seçmek “stateless ve otomatik güvenli” demek değildir. Opaque token seçmek de authorization problemlerini çözmez.

36. JWT sızma testinde hangi kontroller yapılmalı?

JWT testi, token’ı online decoder’a yapıştırıp payload’a bakmak değildir. Üretim token’larının üçüncü taraf sitelere gönderilmesi ayrıca credential leakage yaratabilir.

Bu çalışma daha geniş Sızma Testi Nedir? Kapsamlı Rehber içindeki authentication, authorization ve business logic doğrulamasının JWT’ye özgü bölümüdür. Token formatı ayrı görünse de gerçek etki ancak korunan endpoint ve business action üzerinde doğrulandığında anlam kazanır.

Yetkilendirilmiş testte şu adımlar izlenir:

Token inventory

  • Access token
  • ID token
  • Refresh token
  • Password reset token
  • E-mail verification token
  • Invitation token
  • Service token
  • WebSocket connection token
  • Signed download veya action token

Header validation

  • alg:none
  • Algorithm allowlist
  • Algorithm family confusion
  • typ ve cty
  • kid input handling
  • jku, x5u, jwk, x5c
  • Unknown critical header behavior
  • Duplicate header member davranışı

Claim validation

  • Missing iss, aud, exp, sub
  • Wrong issuer
  • Wrong audience
  • Multiple audience ve azp semantics
  • Expired ve not-yet-valid token
  • Future iat
  • Aşırı clock skew
  • Claim type mismatch
  • Duplicate claim
  • Null, array ve string shape farkları
  • Unknown claim behavior

Token substitution

  • ID token → API access token
  • Refresh token → access token
  • Password reset token → session token
  • Test issuer → production API
  • Service A audience → Service B
  • Tenant A issuer/key → Tenant B
  • Mobile client token → privileged web client

Token lifecycle testleri

  • Logout sonrası replay
  • Password reset sonrası replay
  • Role removal sonrası stale access
  • Refresh rotation
  • Refresh reuse detection
  • Concurrent refresh
  • Device revoke
  • Key rotation
  • Unknown kid davranışı

Storage ve leakage

  • URL
  • Cookie attribute’ları
  • Browser storage
  • Mobile Keychain/Keystore
  • Log ve APM
  • Error response
  • Referrer
  • Crash report
  • Source map ve debug output

API ve mobil client’ın token lifecycle’ını birlikte test etmenin kapsamını API ve Mobil Uygulama İçin Ayrı Sızma Testi Gerekir mi? yazısında ele alıyoruz.

37. Source code review sırasında JWT güvenliği nasıl incelenir?

Kod incelemesi yalnız jwt.verify string’ini aramaz. Token’ın üretildiği, validate edildiği ve authorization context’e dönüştürüldüğü bütün path izlenir.

Application genelindeki source-to-sink ve trust boundary yaklaşımını Kaynak Kod Analizi Nedir? Kapsamlı Rehber içinde ele alıyoruz. JWT review bu yaklaşımı issuer configuration, key selection, claim mapping ve endpoint policy boyunca uygular.

Aranacak configuration pattern’leri

verify_signature = false
ValidateIssuer = false
ValidateAudience = false
ValidateLifetime = false
RequireHttpsMetadata = false
clockSkew = very_large_value
algorithms = multiple_unrelated_families
decodeJwt(...) used for authentication
jku or x5u read from token header

Trust flow soruları

  • Token’ı hangi component üretiyor?
  • Private key’e kim erişiyor?
  • Resource server expected issuer’ı nereden alıyor?
  • Audience her service için ayrı mı?
  • Token type ayrımı var mı?
  • Claim mapping merkezi mi?
  • Role ve scope hangi code’da permission’a dönüşüyor?
  • Object authorization hangi layer’da uygulanıyor?
  • Refresh state atomic mi?
  • Logout ve revocation event’i nereye yayılıyor?
  • JWKS cache ve rotation nasıl çalışıyor?
  • Test profile production’da aktive olabilir mi?

Negative security testleri

CI/CD içinde yalnız valid token happy path’i olmamalıdır:

tests:
  - name: reject_wrong_issuer
    token: valid_signature_wrong_iss
    expected_status: 401

  - name: reject_wrong_audience
    token: valid_signature_wrong_aud
    expected_status: 401

  - name: reject_id_token_at_api
    token: valid_oidc_id_token
    expected_status: 401

  - name: reject_duplicate_audience_claim
    token: valid_signature_duplicate_aud
    expected_status: 401

  - name: reject_unknown_critical_header
    token: valid_signature_unknown_crit
    expected_status: 401

  - name: reject_workload_token_at_user_endpoint
    token: valid_client_credentials_token
    endpoint: /api/me/orders
    expected_status: 403

  - name: deny_missing_scope
    token: valid_access_token_without_orders_write
    expected_status: 403

  - name: deny_cross_tenant_object
    token: tenant_a_valid_token
    resource: tenant_b_order
    expected_status: 404

401 ile 403 ayrımı da önemlidir. Invalid token authentication aşamasında 401 üretir. Valid token gerekli authorization’a sahip değilse 403 düşünülebilir. Object enumeration riskinde resource policy 404 tercih edebilir.

38. JWT bulgusu nasıl attack chain’e dönüşür?

Tek başına bir configuration hatasının etkisi sınırlı görünebilir. Gerçek risk çoğu zaman birkaç zayıflığın birleşiminden doğar.

Attack chain 1: Test issuer’dan production erişimi

Production API issuer doğrulamıyor
        ↓
Test ve production aynı signing key'i veya ortak trust store'u kullanıyor
        ↓
Test ortamında kullanıcı veya role üretilebiliyor
        ↓
Test token'ı production API'de geçiyor
        ↓
Production data access

Remediation yalnız ValidateIssuer=true yapmakla sınırlı değildir. Environment key separation, audience ayrımı, test account governance ve regression testi de gerekir.

Attack chain 2: ID token substitution ve eksik audience

Client geçerli ID token alıyor
        ↓
API access token type'ını doğrulamıyor
        ↓
API audience kontrol etmiyor
        ↓
ID token bearer credential olarak kabul ediliyor
        ↓
Scope enforcement zayıfsa API erişimi oluşuyor

Attack chain 3: XSS üzerinden bearer token replay

SPA localStorage'da uzun ömürlü access token tutuyor
        ↓
Stored XSS token'ı okuyor
        ↓
Token geniş audience ve scope taşıyor
        ↓
Revocation ve sender constraint yok
        ↓
Saldırgan kendi cihazından token'ı replay ediyor

Burada remediation yalnız storage değiştirmek değildir. XSS root cause’u, token ömrü, audience, scope, revocation ve CSP birlikte ele alınır.

Attack chain 4: Stale admin role

Admin role token'a gömülüyor
        ↓
Access token 24 saat geçerli
        ↓
Kullanıcı admin rolünden çıkarılıyor
        ↓
Resource server current entitlement kontrol etmiyor
        ↓
Eski token ile privileged action devam ediyor

JWT bulgusu raporlanırken yalnız malformed token response’u değil, hangi business action’ın yapılabildiği gösterilmelidir.

39. Severity nasıl belirlenmeli?

Her JWT hatası critical değildir. Severity şu faktörlerle belirlenir:

  • Attacker kendi token’ını üretebiliyor mu?
  • Başka trusted issuer’dan token almak yeterli mi?
  • Authentication gerekiyor mu?
  • Wrong audience token kolayca elde edilebiliyor mu?
  • Token type substitution gerçek endpoint’te çalışıyor mu?
  • Hata arbitrary role veya subject seçimine izin veriyor mu?
  • Etkilenen API internetten erişilebilir mi?
  • Token hangi scope ve action’lara ulaşıyor?
  • Cross-tenant veya admin etkisi var mı?
  • Token ömrü ve revocation davranışı nedir?
  • Exploit yalnız debug environment’ta mı?
  • Compensating authorization kontrolü var mı?
BulguOlası etkiTipik öncelik yaklaşımı
alg:none gerçekten kabul ediliyorToken forgery ve authentication bypassÇoğu authenticated sistemde Critical
Weak HMAC secret offline kırılabiliyorArbitrary signed tokenKey yetkisine göre Critical veya High
Audience doğrulanmıyorCross-service token substitutionElde edilebilir token ve privilege’e göre High/Medium
Issuer doğrulanmıyorUntrusted issuer token’ı kabulüTrust modeline göre Critical/High
ID token access token olarak geçiyorScope ve API boundary bypassErişilen action’a göre High/Medium
Access token 24 saat geçerliTheft window genişStorage ve revocation’a göre Medium/Low
Token log’a yazılıyorCredential exposureLog erişimi ve token yetkisine göre High/Medium
typ yok, diğer profile kuralları güçlüDefense-in-depth eksikliğiContext’e göre Low/Medium
Unknown kid her seferinde JWKS fetch ediyorAvailability ve outbound loadRate ve etkiye göre Medium/Low

Scanner’ın “JWT uses RS256” veya “token lifetime long” demesi tek başına severity kanıtı değildir. Exploitability ve iş etkisi doğrulanmalıdır.

40. JWT güvenliği için geliştirici kontrol listesi

Token üretimi

  • [ ] Her token türü için açık profile tanımlı
  • [ ] Access token ve ID token ayrılmış
  • [ ] Uygun typ değeri kullanılıyor
  • [ ] iss, aud, sub, iat ve exp semantics açık
  • [ ] Access token dar audience ve scope taşıyor
  • [ ] Token içine gereksiz kişisel veya hassas veri konulmuyor
  • [ ] HMAC kullanılıyorsa en az gerekli entropy sağlanıyor
  • [ ] Asymmetric private key secret manager veya HSM ile korunuyor
  • [ ] Signing key environment’lar arasında paylaşılmıyor
  • [ ] Key rotation ve emergency revoke planı var

Token validation

  • [ ] Token yalnız decode edilmiyor, cryptographically verify ediliyor
  • [ ] Algorithm allowlist application tarafından sabitleniyor
  • [ ] Symmetric ve asymmetric algorithm aileleri karıştırılmıyor
  • [ ] alg:none authentication context’inde reddediliyor
  • [ ] Issuer exact value ile doğrulanıyor
  • [ ] Audience current resource server’a göre doğrulanıyor
  • [ ] exp zorunlu ve doğrulanıyor
  • [ ] nbf varsa doğrulanıyor
  • [ ] iat type ve age policy’ye göre kontrol ediliyor
  • [ ] Clock skew küçük ve gerekçeli
  • [ ] Token type doğrulanıyor
  • [ ] Required claim ve claim shape kontrol ediliyor
  • [ ] Duplicate claim/header davranışı güvenli
  • [ ] Unknown critical header token’ı fail-closed reddediyor

Key ve JWKS

  • [ ] kid path, query veya command içine doğrudan eklenmiyor
  • [ ] jku ve x5u token’dan arbitrary URL kabul etmiyor
  • [ ] JWKS URL trusted issuer metadata’sına bağlı
  • [ ] JWKS yalnız HTTPS üzerinden alınıyor
  • [ ] Redirect, timeout ve response size sınırları var
  • [ ] Cache ve minimum refresh interval tanımlı
  • [ ] Unknown kid flood kontrolü var
  • [ ] Key type, use, algorithm ve boyut doğrulanıyor
  • [ ] Eski key’in kaldırılma süresi token ömrüyle uyumlu
  • [ ] Private key access audit ediliyor

Authorization

  • [ ] Scope exact value olarak parse ediliyor
  • [ ] Role mapping issuer ve application bağlamına bağlı
  • [ ] User-delegated ve workload token profile’ları ayrılmış
  • [ ] sub, client_id, azp ve actor semantics birbirine karıştırılmıyor
  • [ ] Object-level authorization her request’te uygulanıyor
  • [ ] Tenant isolation resource query’sinde enforce ediliyor
  • [ ] Field-level authorization yalnız DTO’ya bırakılmıyor
  • [ ] Critical action current business state’i kontrol ediyor
  • [ ] Stale role ve entitlement riski yönetiliyor
  • [ ] Hassas action için re-authentication veya step-up düşünülüyor

Session ve token lifecycle

  • [ ] Access token ömrü risk bazlı ve sınırlı
  • [ ] Refresh token yüksek entropy’li
  • [ ] Public client için rotation veya sender constraint var
  • [ ] Refresh reuse detection atomic çalışıyor
  • [ ] Logout refresh state’i server-side revoke ediyor
  • [ ] Password reset ve account disable aktif session’ları etkiliyor
  • [ ] Device revoke uygulanabiliyor
  • [ ] Token theft ve replay event’leri izleniyor
  • [ ] Revocation’ın service’lere yayılma süresi ölçülüyor

Storage ve transport

  • [ ] Token URL query parameter’da taşınmıyor
  • [ ] TLS zorunlu
  • [ ] Authorization ve Cookie header’ları log’da redact ediliyor
  • [ ] Browser storage kararı threat model ile verilmiş
  • [ ] Cookie kullanılıyorsa Secure, HttpOnly ve uygun SameSite var
  • [ ] CSRF ayrıca kontrol ediliyor
  • [ ] Mobile token Keychain veya Keystore’da uygun policy ile saklanıyor
  • [ ] Third-party SDK, crash report ve analytics token almıyor
  • [ ] Support ve debug çıktıları token göstermiyor

42. Sonuç: JWT güvenliği validator’ın güven sözleşmesidir

JWT’nin üç parçadan oluştuğunu bilmek veya token’a exp eklemek güvenli bir authentication architecture kurmaya yetmez.

Güvenli resource server şu soruların tamamına kesin cevap verir:

  1. 1Bu token hangi issuer’dan kabul edilir?
  2. 2Hangi audience için üretilmiş olmalıdır?
  3. 3Hangi algorithm ve key kullanılabilir?
  4. 4Hangi token type kabul edilir?
  5. 5Hangi claim’ler zorunludur?
  6. 6Token hangi zaman aralığında geçerlidir?
  7. 7Claim’ler hangi authorization policy’ye dönüşür?
  8. 8Resource ownership ve tenant isolation nerede enforce edilir?
  9. 9Token çalınırsa kullanım penceresi nasıl sınırlandırılır?
  10. 10Logout, role değişimi ve account disable nasıl yayılır?
  11. 11Key rotation sırasında güvenlik ve availability nasıl korunur?
  12. 12Hatalı token’ların reddedildiği regression test ile kanıtlanıyor mu?

JWT güvenliğinde iyi configuration, bu cevapları token’ın kendi header’ından öğrenmez. Trusted application configuration, issuer metadata, dar validation profile ve server-side authorization policy üzerinden tanımlar.

SECNODEX Sızma Testi hizmeti kapsamında web, API ve mobil uygulamalardaki JWT kullanımını yalnız signature bypass açısından değerlendirmez. Token issuance, validation, key trust, claim mapping, session lifecycle ve resource authorization zincirini manuel testlerle doğrular. Bulguları yeniden üretilebilir PoC, request/response evidence, iş etkisi, root cause, uygulanabilir remediation ve retest sonucu ile raporlar.

JWT configuration’ını runtime davranışla birlikte değerlendirmek veya authentication code path’ini Kaynak Kod Analizi hizmeti kapsamında incelemek için SECNODEX ile iletişime geçebilirsiniz.

Kaynaklar

#JWT Güvenliği#JSON Web Token#API Security#Authentication#Authorization#OAuth 2.0#OpenID Connect#Session Management

Sık sorulan sorular

JWT güvenli midir?

JWT doğru profile ve validation policy ile güvenli biçimde kullanılabilir. Risk çoğu zaman formatın kendisinden değil, algorithm, key, issuer, audience, lifetime, token type ve authorization kontrollerinin eksik yapılandırılmasından doğar.

JWT payload şifreli midir?

Signed JWS olarak kullanılan tipik üç parçalı JWT’nin payload’u şifreli değildir. Base64url decode edilebilir. Confidentiality gerekiyorsa JWE veya başka uygun encryption tasarımı gerekir. Yine de minimum veri ilkesi uygulanmalıdır.

JWT decode etmek signature doğrulamak mıdır?

Hayır. Decode yalnız header ve payload’u okur. Signature veya MAC validation yapmaz. Decode edilmiş claim’ler validation tamamlanmadan authentication veya authorization kararında kullanılmamalıdır.

`alg:none` açığı hâlâ var mı?

Modern library’lerin çoğu bunu default olarak reddeder. Ancak eski dependency, custom parser, debug flag veya validation’ı kapatılmış bir downstream service nedeniyle hâlâ test edilmelidir. Güvenli yaklaşım yalnız none yasaklamak değil beklenen algorithm’i allowlist etmektir.

HS256 mı RS256 mı daha güvenlidir?

İkisi doğru key ve implementation ile kullanılabilir. HS256 shared secret kullanır ve bütün validator’ların signing yetkisine sahip olmasına yol açabilir. RS256 private signing key ile public verification key’i ayırır. Çok sayıda resource server bulunan mimarilerde bu trust separation avantaj sağlar.

JWT için secret ne kadar uzun olmalı?

HS256 için RFC 7518 en az 256-bit key ister. İnsan tarafından seçilmiş password yerine CSPRNG ile üretilmiş key kullanılmalıdır. Key’in secret manager’da korunması da uzunluğu kadar önemlidir.

`iss` ve `aud` doğrulamak neden gerekli?

iss, token’ı hangi trusted issuer’ın ürettiğini doğrular. aud, token’ın bu resource server için üretildiğini gösterir. Bunlardan biri eksikse başka issuer veya başka API için üretilmiş geçerli token bu application’da kabul edilebilir.

ID token API’ye gönderilebilir mi?

ID token client’a authentication sonucu taşır. API erişimi için access token kullanılmalıdır. Resource server ID token’ı bearer access token yerine kabul etmemeli, token type ve audience doğrulamalıdır.

JWT ile logout nasıl yapılır?

Client’taki token’ı silmek yalnız local logout sağlar. Kısa ömürlü access token, server-side refresh token revocation, gerekirse session version veya jti denylist ve hassas action’larda re-authentication birlikte kullanılabilir.

Refresh token JWT olmak zorunda mı?

Hayır. Yüksek entropy’li opaque refresh token, server tarafında hash, family, status ve expiry ile yönetilebilir. Bu model rotation ve targeted revocation için çoğu zaman daha anlaşılırdır.

JWT `localStorage` içinde saklanmalı mı?

Tek bir doğru storage cevabı yoktur. localStorage XSS ile okunabilir. HttpOnly cookie token okumayı zorlaştırır, fakat CSRF’yi ayrıca yönetmek gerekir. BFF, in-memory storage veya platform secure storage architecture ve threat model’e göre değerlendirilmelidir.

JWT sızma testinde ne kontrol edilir?

Algorithm ve signature validation yanında issuer, audience, time claim’leri, token type substitution, kid ve remote key trust, JWKS rotation, claim mapping, authorization, revocation, refresh rotation, storage ve leakage test edilir. Amaç yalnız token üretmek değil gerçek API etkisini kanıtlamaktır.

Siber güvenlik çalışmanızı
SECNODEX ile planlayın

İhtiyacınız tek bir uygulamanın testinden kurum genelinde bir Red Team çalışmasına kadar uzanabilir. Ekibimiz scope’u, kritik varlıkları ve beklenen çıktıları sizinle netleştirir. Ardından iş hedefinize ve risk önceliklerinize uygun, sınırları belirlenmiş bir çalışma planı sunar.