Mert Özen Her Satırda Daha İleri
Backend .NET Core Web API — #20

JWT ile kimlik doğrulama: Token nasıl üretilir, nasıl doğrulanır?

Mert Özen 15 Ağu 2026 14 dk 32 görüntülenme
JWT ile kimlik doğrulama: Token nasıl üretilir, nasıl doğrulanır?

JWT ile kimlik doğrulamayı işliyoruz: token nedir, üç parçası ne anlama gelir, neden imzalanır, sunucu neden durum tutmaz ve .NET'te token üretip doğrulamayı adım adım kuruyoruz.

Önceki yazıda authentication'ın "sen kimsin?" sorusunu sorduğunu konuşmuştuk. Peki bu soruyu bir kez cevapladıktan sonra ne oluyor? Kullanıcı her istekte kullanıcı adı ve şifresini tekrar tekrar göndermek zorunda mı? Elbette hayır. İşte JWT tam bu noktada devreye giriyor. Bugün bir kullanıcının kimliğini bir kez doğrulayıp, sonraki isteklerde bunu nasıl güvenle taşıdığımızı konuşuyoruz.

Sorun: Sunucu Kullanıcıyı Nasıl Hatırlasın?

Bir şeyi hatırlaman lazım: HTTP, doğası gereği durumsuzdur (stateless). Yani her istek, kendinden öncekilerden habersizdir. Sunucu, bir isteği işleyip bittiğinde onu unutur. Bir sonraki istek geldiğinde, o kişinin az önce giriş yapmış kullanıcı olduğunu kendiliğinden bilmez. Peki kullanıcı bir kez giriş yaptıktan sonra, sunucu onu sonraki isteklerde nasıl tanıyacak?

Klasik bir yol vardı: sunucu, giriş yapan her kullanıcı için kendi belleğinde bir kayıt (session) tutardı. Ama bu yaklaşımın bir maliyeti var; sunucu her kullanıcıyı hatırlamak zorunda ve birden fazla sunucun varsa bu hafızayı paylaşmak zorlaşıyor. JWT ise bambaşka, zarif bir yol sunuyor: sunucu hiçbir şey hatırlamıyor, bilgiyi kullanıcının kendisi taşıyor.

JWT Nedir?

JWT, "JSON Web Token" demek. Kısaca, kullanıcının kimliğini içeren, imzalı bir metin parçası. Kullanıcı giriş yaptığında sunucu ona bu token'ı verir. Kullanıcı da sonraki her istekte bu token'ı yanında getirir; sanki bir giriş bileti gibi. Sunucu bileti kontrol eder, geçerliyse "tamam, seni tanıdım" der ve isteği işler.

Önceki yazıdaki otel benzetmesini hatırla: resepsiyon sana bir oda kartı vermişti. İşte JWT tam olarak o oda kartıdır. Bir kez alırsın, sonra her kapıda gösterirsin. Otelin her seferinde kim olduğunu resepsiyondan teyit etmesine gerek yoktur; kartın kendisi kimliğini kanıtlar. JWT de böyle çalışır: token'ın kendisi, kimliğinin taşınabilir kanıtıdır.

Token'ın Üç Parçası

Bir JWT, nokta işaretiyle ayrılmış üç bölümden oluşur. Her bölümün ayrı bir görevi var.

Header (başlık): Token'ın türünü ve hangi imzalama yönteminin kullanıldığını söyler. Teknik bir üst bilgi.

Payload (yük): İşin özü burada. Kullanıcıya dair bilgiler burada taşınır: kim olduğu, hangi rollere sahip olduğu, token'ın ne zaman geçersiz olacağı gibi. Bu bilgilere claim denir.

Signature (imza): Token'ın güvenliğini sağlayan kısım. Sunucu, header ve payload'ı gizli bir anahtarla imzalar. Bu imza sayesinde token'ın içeriğiyle oynanıp oynanmadığı anlaşılır.

Kritik Nokta: JWT Şifreli Değil, İmzalı

Burada çok yaygın bir yanlış anlama var, mutlaka doğru oturt. JWT'nin içindeki payload şifreli değildir; sadece kodlanmıştır ve isteyen herkes içindekileri okuyabilir. Yani token'a hassas bir bilgi (mesela şifre) koymak büyük hatadır, çünkü herkes görebilir.

Peki imza ne işe yarıyor? İmza, içeriği gizlemez; içeriğin değiştirilmediğini garanti eder. Biri token'ın içindeki "ben normal kullanıcıyım" bilgisini alıp "ben yöneticiyim" diye değiştirmeye kalkarsa, imza tutmaz ve sunucu token'ı reddeder. Çünkü geçerli imzayı üretmek için sunucunun gizli anahtarı gerekir ve o anahtar sadece sunucuda vardır. Kısacası: JWT'nin içini herkes okuyabilir ama kimse fark edilmeden değiştiremez. Güvenlik gizlilikten değil, bu değiştirilemezlikten gelir.

.NET'te JWT Kurulumu

Şimdi bunu koda dökelim. Önce gerekli paketi ekliyoruz:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Sonra Program.cs'te JWT doğrulamasını kaydediyoruz. Burası sunucuya "gelen token'ları şu kurallara göre kontrol et" dediğimiz yer:

builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    });

Buradaki en önemli satır IssuerSigningKey: sunucunun token'ları imzalarken ve doğrularken kullandığı gizli anahtar. Bu anahtarı, on beşinci yazıda öğrendiğimiz gibi koda gömmüyoruz; Configuration üzerinden okuyoruz ve gerçekte güvenli bir yerde (User Secrets ya da ortam değişkeni) tutuyoruz. Bu anahtar sızarsa, herkes geçerli token üretebilir; o yüzden bir sır gibi korunmalı.

Token Üretmek: Giriş Endpoint'i

Kullanıcı giriş yaptığında ona bir token vermemiz gerekiyor. Bunu bir login endpoint'inde yaparız. Mantık şöyle: kullanıcı adı ve şifreyi kontrol et, doğruysa kullanıcının kimliğini içeren bir token üret ve dön.

[HttpPost("login")]
public IActionResult Login(LoginDto dto)
{
    // Not: burada kullanıcı adı/şifre kontrolü yapılır.
    // Gerçekte şifre veritabanındaki özeti ile karşılaştırılır.

    var claims = new[]
    {
        new Claim(ClaimTypes.Name, dto.Username),
        new Claim(ClaimTypes.Role, "User")
    };

    var key = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(_config["Jwt:Key"]));

    var credentials = new SigningCredentials(
        key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        claims: claims,
        expires: DateTime.UtcNow.AddHours(1),
        signingCredentials: credentials);

    var tokenString = new JwtSecurityTokenHandler().WriteToken(token);

    return Ok(new { token = tokenString });
}

Adım adım ne olduğuna bakalım. Önce token'a koyacağımız claim'leri hazırlıyoruz; burada kullanıcının adını ve rolünü ekledik. Sonra gizli anahtarımızla imzalama bilgilerini oluşturuyoruz. Ardından bir token nesnesi kuruyor, ona claim'leri, bir son kullanma tarihini (burada bir saat) ve imza bilgilerini veriyoruz. Son kullanma tarihi önemli: token sonsuza dek geçerli olmamalı, çalınırsa zararı sınırlı kalsın diye bir ömrü olmalı. En sonda token'ı metne çevirip kullanıcıya dönüyoruz. Kullanıcı artık bu metni saklayacak ve her istekte getirecek.

Token'ı Kullanmak

Kullanıcı token'ı aldıktan sonra, korumalı bir endpoint'e istek atarken onu Authorization başlığında taşır. Başlığın biçimi şöyledir: Authorization: Bearer <token>. "Bearer" kelimesi, "bu token'ı taşıyan kişi" anlamına gelir. Sunucu bu başlığı görür, token'ın imzasını gizli anahtarıyla kontrol eder, geçerliyse ve süresi dolmamışsa kullanıcıyı tanır ve isteği işler.

Bu noktada önceki yazıdaki [Authorize] attribute'u anlam kazanıyor. Bir endpoint'in başına [Authorize] koyduğunda, .NET gelen token'ı otomatik kontrol eder; geçerli token yoksa 401 döner, varsa içeri alır. Sen bu kontrolü elle yazmazsın; JWT kurulumunu bir kez yaptıktan sonra [Authorize] gerisini halleder.

Küçük Bir Deneme

Projene JWT kurulumunu ekle ve basit bir login endpoint'i yaz; şimdilik kullanıcı adı/şifre kontrolünü sahte tutabilirsin, amaç token üretmeyi görmek. Login'e istek atıp dönen token'ı al. Sonra bu token'ı bir yere kopyala ve jwt.io gibi bir siteye yapıştır; token'ın üç parçasını ve içindeki claim'leri kendi gözünle gör. İçeriğin okunabilir olduğunu, yani şifreli değil kodlanmış olduğunu fark et. Ardından [Authorize] ile korunmuş bir endpoint'e önce token'sız, sonra token ile istek at ve 401 ile 200 farkını gözlemle. Bu deney, JWT'nin tüm döngüsünü tek seferde somutlaştırır.

Bir sonraki yazıda yetkilendirmeyi derinleştireceğiz: role ve policy tabanlı authorization'a geçeceğiz. Bugün token'a koyduğumuz o "rol" bilgisini kullanarak, farklı kullanıcı gruplarına farklı yetkiler vermeyi konuşacağız. Token içindeki claim'ler, o yetki kararlarının tam da dayanağı olacak.