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.