JWT (JSON Web Token), taraflar arasında güvenli bilgi aktarımı için yaygın kullanılan bir standarttır (RFC 7519). Stateless kimlik doğrulama için idealdir; ancak yanlış implementasyon ciddi güvenlik açıkları oluşturur. Bu yazıda PHP'de doğru JWT implementasyonunu ve kaçınılması gereken tuzakları anlatıyorum.
JWT Nasıl Çalışır?
Bir JWT üç bölümden oluşur, nokta ile ayrılır: header.payload.signature
- Header: Algoritma ve token tipi (genellikle
{"alg":"HS256","typ":"JWT"}) - Payload: Claims — kullanıcı bilgisi, son kullanım tarihi vb.
- Signature: Header + payload'ın gizli anahtar ile imzalanması
PHP'de Güvenli Implementasyon
firebase/php-jwt paketini kullanmanızı öneririm. Sıfırdan JWT kodlamak güvenlik açığı riski taşır:
composer require firebase/php-jwt
// Token oluşturma
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$payload = [
'iss' => 'ozcanarslan.com.tr', // issuer
'sub' => $user->id, // subject
'iat' => time(), // issued at
'exp' => time() + 3600, // 1 saat geçerlilik
'jti' => bin2hex(random_bytes(16)), // unique ID
];
$token = JWT::encode($payload, $_ENV['JWT_SECRET'], 'HS256');
// Token doğrulama
try {
$decoded = JWT::decode($token, new Key($_ENV['JWT_SECRET'], 'HS256'));
$userId = $decoded->sub;
} catch (\Exception $e) {
http_response_code(401);
echo json_encode(['error' => 'Geçersiz token']);
exit;
}
Kritik Güvenlik Hataları
Kaçınmanız Gereken Hatalar
- Algoritma "none" kabul etmek: Saldırgan imzayı atlayabilir. Her zaman algoritmayı whitelist'e alın.
- Secret key'i düşük entropyda tutmak: En az 256-bit random bir anahtar kullanın:
openssl rand -base64 32 - Hassas veri payload'a koymak: JWT imzalıdır ama şifrelenmemiştir — herkes decode edebilir
- exp olmadan token oluşturmak: Süresiz token = kalıcı güvenlik açığı
- Token'ı localStorage'da saklamak: XSS saldırısına açık. HttpOnly cookie tercih edin.
Token Yenileme Stratejisi
Access token kısa ömürlü (15-60 dk), refresh token uzun ömürlü (7-30 gün) olmalıdır. Refresh token'ı veritabanında saklayın ve gerektiğinde iptal edebilin:
// Refresh token tablosu
Schema::create('refresh_tokens', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('token', 64)->unique();
$table->timestamp('expires_at');
$table->timestamp('revoked_at')->nullable();
$table->string('ip_address', 45)->nullable();
$table->timestamps();
$table->index(['token', 'revoked_at']);
});
Laravel'de JWT Middleware
public function handle(Request $request, Closure $next): Response
{
$header = $request->header('Authorization', '');
if (!str_starts_with($header, 'Bearer ')) {
return response()->json(['error' => 'Token gerekli'], 401);
}
$token = substr($header, 7);
try {
$payload = JWT::decode($token, new Key(config('jwt.secret'), 'HS256'));
$request->merge(['auth_user_id' => $payload->sub]);
} catch (ExpiredException $e) {
return response()->json(['error' => 'Token süresi dolmuş', 'code' => 'TOKEN_EXPIRED'], 401);
} catch (\Exception $e) {
return response()->json(['error' => 'Geçersiz token'], 401);
}
return $next($request);
}
Sonuç
JWT doğru kullanıldığında güçlü ve ölçeklenebilir bir kimlik doğrulama mekanizmasıdır. Kısa ömürlü access token, veritabanına kayıtlı refresh token ve HttpOnly cookie kombinasyonu production için en güvenli yaklaşımdır.