JWT Nedir: Yapısı ve Çalışma Mantığı
Modern web uygulamalarında kimlik doğrulama ve yetkilendirme mekanizmaları, güvenli bir sistem inşa etmenin temel taşlarından biridir. Klasik session tabanlı yaklaşımların yerini giderek daha fazla token tabanlı sistemler alıyor ve bu sistemlerin en popüleri de şüphesiz JWT. Ancak pek çok geliştirici ve sysadmin JWT’yi kullanıyor olsa da altta ne döndüğünü tam olarak kavramadan, sadece kütüphane çağrıları yaparak ilerliyor. Bu yazıda JWT’nin içini dışına çevireceğiz.
JWT Nedir ve Neden Ortaya Çıktı
JSON Web Token (JWT), iki taraf arasında güvenli bilgi aktarımı sağlamak için kullanılan, kendine yeten ve kompakt bir token formatıdır. RFC 7519 standardıyla tanımlanmıştır. “Kendine yeten” derken kastettiğim şu: token’ın içinde kullanıcı hakkındaki bilgiler zaten mevcut, sunucunun her istek için veritabanına gidip “bu kullanıcı kim?” diye sormasına gerek yok.
Klasik session tabanlı sistemde şöyle bir akış var:
- Kullanıcı giriş yapar, sunucu bir session ID üretir
- Session ID hem sunucuda (genellikle Redis veya veritabanında) hem de kullanıcının cookie’sinde saklanır
- Her istekte sunucu cookie’yi okur, kendi deposundaki session ile karşılaştırır
- Eşleşirse kullanıcı doğrulanmış kabul edilir
Bu yaklaşımın sorunları var. Özellikle microservice mimarilerinde ve yatay ölçeklendirme yapılan sistemlerde her sunucunun session store’a erişmesi gerekiyor. Bu da merkezi bir session deposu anlamına geliyor, yani potansiyel bir single point of failure. JWT bu sorunu şöyle çözüyor: token’ın kendisi zaten tüm bilgileri taşıyor ve imzalanmış durumda. Herhangi bir sunucu bu imzayı doğrulayabiliyor.
JWT’nin Anatomisi
Bir JWT token’ı üç parçadan oluşur ve bu parçalar nokta karakteriyle birbirinden ayrılır:
xxxxx.yyyyy.zzzzz
Header.Payload.Signature
Gerçek bir token şöyle görünür:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFobWV0IFlpbG1heiIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
İlk bakışta anlamsız gelen bu string aslında Base64URL ile kodlanmış yapılandırılmış veriler içeriyor.
Header (Başlık)
Header kısmı genellikle iki bilgi içerir: token tipi ve kullanılan imzalama algoritması.
# Header'ı decode etmek için:
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d
# Çıktı: {"alg":"HS256","typ":"JWT"}
Yaygın kullanılan algoritmalar:
- HS256: HMAC-SHA256, simetrik anahtar kullanır. Hem imzalama hem doğrulama aynı gizli anahtarla yapılır
- HS384: HMAC-SHA384, daha güçlü ama daha yavaş
- RS256: RSA-SHA256, asimetrik anahtar çifti kullanır. Private key ile imzalanır, public key ile doğrulanır
- ES256: ECDSA-SHA256, eliptik eğri kriptografisi kullanır, RS256’ya göre daha kısa anahtarlar üretir
- none: İmzasız token, ASLA production’da kullanmayın
Payload (Yük)
Payload, token’ın asıl bilgi taşıyan kısmıdır. İçindeki her bir bilgi parçasına claim (iddia/talep) denir. Üç tür claim vardır.
Registered Claims (Kayıtlı, standart claim’ler):
- iss: Issuer, token’ı kim üretdi (örn: “auth.sirketim.com”)
- sub: Subject, token kimin için üretildi (genellikle kullanıcı ID’si)
- aud: Audience, token hangi servis için geçerli (örn: “api.sirketim.com”)
- exp: Expiration Time, token’ın Unix timestamp olarak son geçerlilik zamanı
- nbf: Not Before, token bu zamandan önce geçersizdir
- iat: Issued At, token’ın üretildiği zaman
- jti: JWT ID, token’ın benzersiz ID’si (replay attack önlemek için)
Public Claims: IANA JWT Claims Registry’de kayıtlı, genel kullanım için tanımlanmış claim’ler.
Private Claims: Uygulamanıza özel claim’ler. İstediğiniz her şeyi ekleyebilirsiniz.
# Payload'ı decode etmek:
echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFobWV0IFlpbG1heiIsImlhdCI6MTUxNjIzOTAyMn0" | base64 -d
# Çıktı: {"sub":"1234567890","name":"Ahmet Yilmaz","iat":1516239022}
Önemli bir not: Payload Base64URL ile sadece encode edilmiştir, şifrelenmemiştir. Yani token’ı ele geçiren herkes içeriği okuyabilir. Hassas bilgileri (şifre, kredi kartı numarası, vb.) payload’a koymayın.
Signature (İmza)
İmza, token’ın gerçekliğini ve bütünlüğünü doğrulayan kısımdır. HS256 algoritması için imza şu şekilde üretilir:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
)
Sunucu token’ı doğrularken aynı işlemi tekrar yapar ve ürettiği imza ile token’daki imzayı karşılaştırır. Eğer payload’da tek bir karakter bile değiştirilmişse imza tutmaz ve token geçersiz sayılır.
Pratik Uygulamalar: JWT Üretme ve Doğrulama
Gerçek dünya senaryolarında JWT ile nasıl çalışıldığını görelim. Önce bash ile basit bir token decode işlemi:
#!/bin/bash
# jwt_decode.sh - JWT token'ı decode etmek için basit script
TOKEN=$1
if [ -z "$TOKEN" ]; then
echo "Kullanim: $0 <jwt_token>"
exit 1
fi
# Token'ı parçalara ayır
IFS='.' read -ra PARTS <<< "$TOKEN"
HEADER="${PARTS[0]}"
PAYLOAD="${PARTS[1]}"
# Base64URL padding düzeltmesi
pad_base64() {
local str="$1"
local mod=$((${#str} % 4))
if [ $mod -eq 2 ]; then str="${str}=="; fi
if [ $mod -eq 3 ]; then str="${str}="; fi
echo "$str" | tr '_-' '/+'
}
echo "=== HEADER ==="
pad_base64 "$HEADER" | base64 -d 2>/dev/null | python3 -m json.tool
echo ""
echo "=== PAYLOAD ==="
pad_base64 "$PAYLOAD" | base64 -d 2>/dev/null | python3 -m json.tool
Node.js ile JWT üretme ve doğrulama (en yaygın kullanım senaryosu):
# jsonwebtoken paketini kur
npm install jsonwebtoken
# Basit bir token üretme scripti oluştur
cat > jwt_demo.js << 'EOF'
const jwt = require('jsonwebtoken');
const SECRET_KEY = process.env.JWT_SECRET || 'gizli-anahtar-buraya';
// Token üret
function createToken(userId, email, role) {
const payload = {
sub: userId,
email: email,
role: role,
iss: 'auth.sirketim.com',
aud: 'api.sirketim.com'
};
return jwt.sign(payload, SECRET_KEY, {
expiresIn: '1h',
algorithm: 'HS256'
});
}
// Token doğrula
function verifyToken(token) {
try {
const decoded = jwt.verify(token, SECRET_KEY, {
issuer: 'auth.sirketim.com',
audience: 'api.sirketim.com'
});
console.log('Token geçerli:', decoded);
return decoded;
} catch (err) {
console.error('Token hatalı:', err.message);
return null;
}
}
const token = createToken('usr_001', '[email protected]', 'admin');
console.log('Üretilen token:', token);
console.log('');
verifyToken(token);
EOF
node jwt_demo.js
Python ile JWT işlemleri, özellikle backend servislerde sıkça kullanılan bir senaryo:
pip install PyJWT cryptography
python3 << 'EOF'
import jwt
import datetime
import os
SECRET_KEY = os.getenv('JWT_SECRET', 'super-gizli-anahtar')
# Token oluştur
def create_token(user_id: str, permissions: list) -> str:
now = datetime.datetime.utcnow()
payload = {
'sub': user_id,
'permissions': permissions,
'iat': now,
'exp': now + datetime.timedelta(hours=2),
'nbf': now,
'jti': f"{user_id}-{int(now.timestamp())}"
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return token
# Token doğrula
def verify_token(token: str) -> dict:
try:
decoded = jwt.decode(
token,
SECRET_KEY,
algorithms=['HS256'],
options={"verify_exp": True}
)
return {'success': True, 'data': decoded}
except jwt.ExpiredSignatureError:
return {'success': False, 'error': 'Token süresi dolmuş'}
except jwt.InvalidTokenError as e:
return {'success': False, 'error': f'Geçersiz token: {str(e)}'}
token = create_token('usr_42', ['read:users', 'write:posts'])
print(f"Token: {token[:50]}...")
print(f"Doğrulama: {verify_token(token)}")
EOF
RS256 ile Asimetrik JWT
Production ortamlarında, özellikle birden fazla servis arasında token paylaşımı yapıldığında RS256 kullanmak çok daha güvenli bir yaklaşımdır. Çünkü token’ı doğrulaması gereken servislerle private key paylaşmak zorunda kalmıyorsunuz, sadece public key yeterli.
# RSA anahtar çifti üret
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
echo "Private key uzunluğu:"
wc -c private_key.pem
echo "Public key içeriği:"
cat public_key.pem
# Python ile RS256 token üret ve doğrula
python3 << 'EOF'
import jwt
with open('private_key.pem', 'r') as f:
private_key = f.read()
with open('public_key.pem', 'r') as f:
public_key = f.read()
# Private key ile imzala
token = jwt.encode(
{'sub': 'service-a', 'scope': 'internal'},
private_key,
algorithm='RS256'
)
print(f"RS256 Token: {token[:60]}...")
# Sadece public key ile doğrula
decoded = jwt.decode(token, public_key, algorithms=['RS256'])
print(f"Decoded: {decoded}")
EOF
JWT’nin Çalışma Akışı: Gerçek Bir Senaryo
Bir e-ticaret platformu düşünün. Kullanıcı mobil uygulamadan giriş yapıyor ve hem ürün API’sine hem de sipariş API’sine istek atıyor. JWT akışı şöyle işliyor:
- Kullanıcı email/şifre ile auth servisine POST isteği atar
- Auth servisi bilgileri doğrular, JWT üretir ve gönderir
- Mobil uygulama token’ı güvenli storage’da saklar
- Her API isteğinde Authorization header’ına token ekler:
Authorization: Bearer - API gateway token’ı doğrular, geçerliyse isteği ilgili servise yönlendirir
Nginx tarafında JWT doğrulama için bir yapılandırma örneği:
# nginx.conf - JWT doğrulama için lua modülü ile
# (nginx-plus veya openresty gerektirir)
cat > /etc/nginx/conf.d/api_gateway.conf << 'EOF'
server {
listen 443 ssl;
server_name api.sirketim.com;
location /api/v1/ {
# JWT doğrulama için auth_request kullan
auth_request /auth/validate;
auth_request_set $user_id $upstream_http_x_user_id;
# Doğrulandıktan sonra user_id'yi upstream'e geçir
proxy_set_header X-User-Id $user_id;
proxy_pass http://backend_servers;
}
location = /auth/validate {
internal;
proxy_pass http://auth_service/validate;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Authorization $http_authorization;
}
# Token endpoint'i auth'a yönlendir
location /auth/ {
proxy_pass http://auth_service/;
}
}
EOF
nginx -t && systemctl reload nginx
Token Süre Yönetimi ve Refresh Token Stratejisi
JWT’nin en kritik yönetim konularından biri token süresidir. Kısa süreli access token ve uzun süreli refresh token kombinasyonu endüstri standardı haline gelmiştir.
# Token süresini kontrol eden bash scripti
check_token_expiry() {
local TOKEN=$1
# Payload'ı decode et
PAYLOAD=$(echo "$TOKEN" | cut -d'.' -f2)
# Padding düzelt ve decode et
PADDED=$(printf '%s' "$PAYLOAD" | tr '_-' '/+' |
awk 'length%4==2{print $0"=="; next}
length%4==3{print $0"="; next}
{print}')
EXP=$(echo "$PADDED" | base64 -d 2>/dev/null |
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('exp',0))")
NOW=$(date +%s)
REMAINING=$((EXP - NOW))
if [ $REMAINING -le 0 ]; then
echo "Token süresi dolmuş! $((REMAINING * -1)) saniye önce"
return 1
elif [ $REMAINING -le 300 ]; then
echo "Uyarı: Token $REMAINING saniye içinde sona erecek"
return 2
else
MINS=$((REMAINING / 60))
echo "Token geçerli. Kalan süre: $MINS dakika"
return 0
fi
}
# Kullanım
# check_token_expiry "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDAwMDAwMDB9.xxx"
Güvenlik Açıkları ve Kaçınılması Gereken Hatalar
JWT kullanımında karşılaşılan yaygın güvenlik hataları gerçekten kritik sonuçlar doğurabiliyor.
“alg: none” Saldırısı: Bazı kütüphanelerin eski sürümleri algoritma alanını körü körüne kabul ediyor. Saldırgan header’ı {"alg":"none"} olarak değiştirip imzayı silerek geçerli token üretebiliyordu. Çözüm: Her zaman izin verilen algoritmaları açıkça belirtin.
Zayıf Secret Key Kullanımı: HS256 için kullanılan secret key yeterince uzun ve rastgele olmalı. “secret”, “password123” gibi zayıf anahtarlar brute force ile kırılabilir.
# Güvenli bir secret key üretme
openssl rand -base64 64
# Veya urandom kullanarak
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 64; echo
# .env dosyasına güvenli kaydetme
echo "JWT_SECRET=$(openssl rand -base64 64)" >> .env
chmod 600 .env
Payload’a Hassas Veri Koymak: Payload sadece encode edilmiş, şifrelenmemiş. Şifre hash’i, kredi kartı, kişisel bilgiler payload’a asla girmemeli.
Token Blacklist Yokluğu: JWT stateless olduğu için bir token’ı “iptal etmek” doğrudan mümkün değil. Kullanıcı logout yaptığında ne olacak? Çözümler:
- Kısa expiry süresi kullanmak (15-30 dakika)
- Kritik durumlarda Redis tabanlı blacklist tutmak
- jti claim’i ile token ID’sini takip etmek
# Redis'te token blacklist kontrolü
redis-cli SET "blacklist:jti:abc123" "1" EX 3600
# Token kontrol
redis-cli EXISTS "blacklist:jti:abc123"
# 1 dönerse token geçersiz sayılır
JWT Debugging ve Monitoring
Production ortamında JWT sorunlarını hızlı tespit etmek için log yapısı önemlidir.
# JWT ile ilgili hataları izlemek için log analizi
# Auth servis loglarından JWT hatalarını filtrele
journalctl -u auth-service --since "1 hour ago" |
grep -E "jwt|token|401|403" |
awk '{print $1, $2, $3, $NF}' |
sort | uniq -c | sort -rn | head -20
# Expired token sayısını izle
tail -f /var/log/nginx/access.log |
grep ' 401 ' |
awk '{print $1, $7}' >> /var/log/jwt_errors.log
Prometheus ile JWT metriklerini toplamak için örnek bir yapılandırma:
# Node.js uygulamasında prometheus ile JWT metrikleri
cat > jwt_metrics.js << 'EOF'
const promClient = require('prom-client');
const jwtVerificationTotal = new promClient.Counter({
name: 'jwt_verification_total',
help: 'JWT doğrulama işlem sayısı',
labelNames: ['status', 'error_type']
});
const jwtVerificationDuration = new promClient.Histogram({
name: 'jwt_verification_duration_seconds',
help: 'JWT doğrulama süresi',
buckets: [0.001, 0.005, 0.01, 0.05, 0.1]
});
function verifyWithMetrics(token, secret) {
const end = jwtVerificationDuration.startTimer();
try {
const result = jwt.verify(token, secret);
jwtVerificationTotal.inc({ status: 'success', error_type: 'none' });
end();
return result;
} catch (err) {
const errorType = err.name || 'unknown';
jwtVerificationTotal.inc({ status: 'failure', error_type: errorType });
end();
throw err;
}
}
EOF
JWT vs Session: Neyi Seçmeli
Her teknolojinin bir use case’i var. JWT her zaman daha iyi değil.
JWT tercih edin:
- Microservice mimarisi kullandığınızda
- Farklı domainler arası authentication gerektiğinde (SSO)
- Stateless API’ler geliştirirken
- Mobile ve SPA uygulamalarda
- Servisler arası iletişimde (service-to-service auth)
Session tercih edin:
- Monolitik web uygulamalarında
- Token iptal etme ihtiyacı kritikse
- Çok sayıda claim taşımanız gerekmiyorsa
- Sunucu tarafında tam kontrol istiyorsanız
Sonuç
JWT, doğru anlaşıldığında ve doğru uygulandığında güçlü bir kimlik doğrulama aracıdır. Yapısını kavramak, sadece teorik bir bilgi değil, doğrudan debugging ve güvenlik kararlarınızı etkiliyor. Token’ın üç parçasını, her parçanın ne anlama geldiğini ve imzalama mekanizmasının nasıl çalıştığını bilmek, ileride “neden 401 alıyorum?” sorusunu çok daha hızlı cevaplamanızı sağlıyor.
Pratik olarak dikkat edilmesi gereken ana noktalar: secret key’inizi güçlü tutun, payload’ı hassas veri deposu olarak kullanmayın, expiry sürelerini makul kısa belirleyin, izin verilen algoritmaları her zaman açıkça belirtin ve production’da token davranışlarını mutlaka log’layıp izleyin. RS256 veya ES256 gibi asimetrik algoritmalara geçmek de, özellikle çoklu servis mimarilerinde, hem güvenlik hem de operasyonel kolaylık açısından değer.
JWT’nin bir sihir değil, standartlaştırılmış bir veri formatı olduğunu aklınızda tutarsanız, onunla ilgili karşılaşacağınız sorunları çok daha kolay çözersiniz.
