easyutils.io / Blog › JWT Security Pitfalls Every Engineer Should Know

JWT Security Pitfalls Every Engineer Should Know

Published: 2026-05-01 · 9 min read

JWTs are simple to use and famously easy to misuse. Most of the security failures aren't in the cryptography — they're in how engineers wire JWTs into application code. Here are the failure modes that actually show up in audits, in order of how often they bite people.

🔐
Decode a JWT safelyInspect header, payload, expiry, and verify HMAC signatures — all in your browser.
Open tool →

1. Algorithm confusion / "alg: none"

Older JWT libraries accept whatever alg the token's header claims. An attacker rewrites the header to {"alg": "none"}, strips the signature, and many libraries verify the token as valid. A variation: a token signed with HS256 using the server's public key as the HMAC secret tricks libraries that auto-pick the algorithm from the header.

Fix: Configure the verifier to accept only the algorithms you actually use. Never call verify() without an explicit algorithm whitelist. Most modern libraries require this, but legacy code paths often don't.

2. Trusting the payload before verifying

It is shockingly common to see code that decodes a JWT to read claims like userId or role and then uses those values before verifying the signature. The decoder is trivial; the signature check is the only thing that proves the token came from your auth server.

Fix: Verify first, then use claims. If you need claims to pick a verification key (rotating keys), check the kid header against your key store rather than trusting payload fields.

3. Long-lived tokens

A JWT with a 30-day expiry is a 30-day all-access pass. Until that token expires, you cannot revoke it server-side without building infrastructure JWTs were designed to avoid.

Fix: Short access tokens (5–30 minutes) plus a longer-lived refresh token stored differently. Revocation hits the refresh path, not every request. If you must keep server-side revocation, store a JTI denylist and check it on every request — but be honest that you've reintroduced session state.

4. Weak HMAC secrets

An HS256 token signed with a 12-character secret is brute-forceable offline. Once captured, an attacker can forge any token they want.

Fix: HMAC secrets should be at least 256 bits of entropy (32 random bytes). For RS256/ES256, key length matters less — algorithm is doing the lifting. Rotate keys regularly and support kid so rotation doesn't require a full deploy.

5. Leaking tokens via storage

Storing JWTs in localStorage exposes them to any XSS — every script that runs in your page can read them. Cookies are not magic protection either, but HttpOnly + Secure + SameSite=Strict cookies are much harder to steal.

Fix: Default to HttpOnly cookies for browser apps unless you have a specific reason not to. For SPAs, this means accepting that you need a session-cookie style architecture and being explicit about CSRF protection.

6. Missing or wrong audience/issuer checks

The iss and aud claims exist so a token issued for service A can't be replayed at service B. Many services check the signature and expiry but never verify aud. A leaked token from one service then opens the door to others sharing the same key.

Fix: Always pin aud to the specific resource server. If you use the same JWT for multiple services, give each service its own expected aud and require it.

7. Clock skew and replay

Servers with different clocks reject tokens that should be valid (or accept tokens that should be expired). Less obvious: a stolen token can be replayed until exp — there is no built-in single-use protection.

Fix: Allow modest clock skew (≤60 seconds) on exp and nbf. For sensitive operations, add a server-side nonce or jti tracking.

8. Confidential data in the payload

JWTs are signed, not encrypted (by default). The payload is plain Base64URL — anyone who sees the token sees the claims. Putting PII, PHI, or sensitive flags in claims means leaking those if the token is ever logged or intercepted.

Fix: Keep payloads to opaque identifiers (sub, scope). For confidentiality, use JWE (encrypted JWT) — but most apps don't need this; they just need to not put secrets in JWS payloads.

9. Confused deputy with kid

If kid is used to look up a key from a database or file, and the lookup is not strictly validated, an attacker can set kid to a path like ../../public.pem or to a SQL fragment.

Fix: Treat kid as untrusted input. Use it only as a key into a curated map of valid keys; never as a filesystem path or SQL parameter.

Quick audit checklist

  1. Verify before reading claims. Always.
  2. Whitelist algorithms; never trust the header's alg alone.
  3. HMAC secret ≥ 256 bits of entropy.
  4. Access tokens ≤ 30 min; longer = refresh token.
  5. Check iss and aud on every verification.
  6. HttpOnly cookies by default; localStorage only with eyes open.
  7. No PII in claims.
  8. kid maps to a validated key list, not a path.
🔐
Decode a JWT safelyInspect header, payload, expiry, and verify HMAC signatures — all in your browser.
Open tool →
easyutils.io / Blog › Her Mühendisin Bilmesi Gereken JWT Güvenlik Tuzakları

Her Mühendisin Bilmesi Gereken JWT Güvenlik Tuzakları

Yayın tarihi: 2026-05-01 · 9 dk okuma

JWT'leri kullanmak kolay; yanlış kullanmak ünlü ölçüde kolay. Güvenlik sorunlarının büyük bölümü kriptografide değil, mühendislerin JWT'leri uygulama koduna nasıl bağladığında. Denetimlerde gerçekten karşılaşılan başarısızlık modları, isabet sıklığına göre sıralı:

🔐
JWT'yi güvenle çözHeader, payload, exp incele ve HMAC imzasını doğrula — tamamı tarayıcında.
Aracı aç →

1. Algoritma karışıklığı / "alg: none"

Eski JWT kütüphaneleri, token'ın header'ında ne yazıyorsa o alg değerini kabul eder. Saldırgan header'ı {"alg": "none"} yapar, imzayı atar ve birçok kütüphane token'ı geçerli sayar. Bir başka varyant: sunucunun public anahtarını HMAC secret olarak kullanan HS256 imzalı bir token; algoritmayı header'dan seçen kütüphaneleri kandırır.

Çözüm: Doğrulayıcıyı sadece gerçekten kullandığınız algoritmaları kabul edecek şekilde yapılandırın. verify() çağrısını asla explicit algorithm whitelist olmadan yapmayın. Modern kütüphanelerin çoğu bunu zorunlu kılıyor; legacy kod yolları çoğu zaman zorlamaz.

2. Doğrulamadan önce payload'a güvenmek

JWT'yi decode edip userId veya role gibi claim'leri okuyup, imzayı doğrulamadan kullanan kod şaşırtıcı derecede yaygın. Decoder trivial; imza kontrolü token'ın auth sunucusundan geldiğini ispatlayan tek şeydir.

Çözüm: Önce verify, sonra claim'leri kullan. Doğrulama anahtarını seçmek için claim'e ihtiyaç varsa (rotating key'ler), payload alanlarına güvenmek yerine kid header'ını anahtar deponuza karşı kontrol edin.

3. Uzun ömürlü token'lar

30 günlük exp'li JWT, 30 günlük her şeye erişim biletidir. Token süresi dolana kadar sunucu tarafında iptal edemezsiniz — JWT'lerin kaçındığı altyapıyı kendiniz kurmadan.

Çözüm: Kısa access token (5–30 dk) + daha uzun ömürlü refresh token, ayrı saklanır. İptal refresh yolunda gerçekleşir, her istekte değil. Sunucu tarafı iptal şartsa JTI deny-list'i tutup her istekte kontrol edin — ama session state'i geri getirdiğinizi bilin.

4. Zayıf HMAC secret'lar

12 karakterli secret ile imzalanmış HS256 token offline brute-force ile kırılır. Yakalandığında saldırgan istediği token'ı üretebilir.

Çözüm: HMAC secret en az 256 bit entropi (32 rastgele byte) olmalı. RS256/ES256 için anahtar uzunluğu daha az kritik — algoritma zaten ağır iş yapıyor. Anahtarları düzenli rotate edin; kid destekleyin ki rotasyon tam deploy gerektirmesin.

5. Saklama yoluyla sızıntı

JWT'yi localStorage'da saklamak, sayfada çalışan her script'in onu okuyabilmesi anlamına gelir — yani her XSS bir token hırsızlığıdır. Cookie de sihirli koruma değil ama HttpOnly + Secure + SameSite=Strict cookie çok daha zor çalınır.

Çözüm: Tarayıcı uygulamaları için varsayılan HttpOnly cookie olsun, belirli bir sebep yoksa. SPA'lar için bu, session-cookie tarzı bir mimari kabul etmek ve CSRF korumasını explicit hâle getirmek demek.

6. Eksik veya yanlış audience/issuer kontrolleri

iss ve aud claim'leri, A servisi için verilmiş bir token'ın B servisinde replay edilememesi için var. Birçok servis imza ve exp'yi kontrol ediyor ama aud'yi asla doğrulamıyor. Bir servisten sızan token aynı anahtarı paylaşan diğer servislere kapı açar.

Çözüm: aud'yi belirli kaynak sunucuya sabitleyin. Aynı JWT'yi birden çok serviste kullanıyorsanız her birine kendi beklenen aud'sini verin ve zorunlu kılın.

7. Saat kayması ve replay

Farklı saatlere sahip sunucular geçerli token'ları reddeder (veya süresi dolmuş olanları kabul eder). Daha az aşikâr olan: çalınan token exp'ye kadar replay edilebilir — yerleşik tek kullanımlık koruma yoktur.

Çözüm: exp ve nbf için makul saat kayması payı (≤60 sn) tanıyın. Hassas işlemler için sunucu tarafı nonce veya jti takibi ekleyin.

8. Payload'da gizli veri

JWT'ler varsayılan olarak imzalıdır, şifreli değil. Payload düz Base64URL — token'ı gören herkes claim'leri görür. PII, sağlık verisi veya hassas flag'leri claim'e koymak, token loglanır veya yakalanırsa o verileri sızdırmak demektir.

Çözüm: Payload'ı opak identifier'larla sınırlı tutun (sub, scope). Gizlilik gerekiyorsa JWE (şifreli JWT) kullanın — ama çoğu uygulamanın buna ihtiyacı yok, sadece JWS payload'ına secret koymamaları yeter.

9. kid ile confused-deputy

kid dosyadan veya veritabanından anahtar lookup için kullanılıyor ve lookup sıkı doğrulanmıyorsa, saldırgan kid'i ../../public.pem gibi bir path'e veya SQL parçasına ayarlayabilir.

Çözüm: kid'i güvenilmez input olarak kabul edin. Sadece kürate edilmiş geçerli anahtarlar haritasında anahtar olarak kullanın; asla filesystem path veya SQL parametresi olarak değil.

Hızlı denetim listesi

  1. Claim okumadan önce doğrula. Her zaman.
  2. Algoritma whitelist; header'daki alg'a tek başına güvenme.
  3. HMAC secret ≥ 256 bit entropi.
  4. Access token ≤ 30 dk; daha uzunsa refresh token.
  5. Her doğrulamada iss ve aud kontrol et.
  6. Varsayılan HttpOnly cookie; localStorage'ı bilerek seç.
  7. Claim'de PII yok.
  8. kid, path değil, doğrulanmış anahtar listesine eşler.
🔐
JWT'yi güvenle çözHeader, payload, exp incele ve HMAC imzasını doğrula — tamamı tarayıcında.
Aracı aç →