ASP.NET CORE AUTHENTİCATİON: JWT, REFRESH TOKEN VE GÜVENLİ API TASARIMI
Modern uygulamalarda “güvenli giriş” çoğu zaman tek bir ekran değil, uçtan uca bir akıştır: kullanıcı oturum açar, API çağrıları yapar, token süresi biter, token yenilenir ve tüm bu süreçte hem performans hem de güvenlik korunur. Özellikle mobil ve SPA senaryolarında JWT tabanlı kimlik doğrulama, doğru kurgulanmadığında sessizce açık bırakabilen detaylar barındırır.
Bu makalede ASP.NET Core JWT kimlik doğrulama yaklaşımını sağlam bir temele oturtacağız: Access token ve refresh token ayrımını netleştirip, token yenileme akışını güvenli biçimde tasarlayacağız. Ardından claim, policy ve role bazlı yetkilendirmeyi ele alacak; anahtar yönetimi, rotasyon, saklama stratejileri ve API sertleştirme (rate limit, log, CORS/CSRF) gibi pratiklerle tasarımı tamamlayacağız.
Hedefimiz, “çalışıyor” seviyesinden “güvenli, sürdürülebilir ve denetlenebilir” seviyesine geçmek. İsterseniz daha kapsamlı uygulamalı akışlar için ASP.NET Core eğitimine de göz atabilirsiniz.

Primary keyword: ASP.NET Core JWT kimlik doğrulama
Başlıktan türetilen primary keywordümüz “ASP.NET Core JWT kimlik doğrulama”. Bu terimi makalenin omurgası olarak düşünün: doğrulama (authentication) katmanının nasıl kurulacağını, token üretimini ve doğrulamasını, ardından yenileme ve yetkilendirme adımlarını bu eksende ele alacağız.
JWT, özellikle mikroservis ve stateless API tasarımlarında popülerdir; ancak “stateless” yaklaşımın, refresh token gibi state gerektiren unsurlarla dengelenmesi gerekir. Bu dengeyi doğru kurmak, hem geliştirici deneyimini iyileştirir hem de saldırı yüzeyini küçültür.
JWT ve Refresh Token’ı doğru konumlandırmak
Access token ne işe yarar?
Access token (erişim belirteci), API’ye yapılan her istekte gönderilen kısa ömürlü bir “yetkilendirme kanıtı”dır. Kısa ömürlü olmasının nedeni basittir: ele geçirilirse saldırganın hareket alanını sınırlamak. JWT’nin içinde kullanıcı kimliği, claim’ler ve token’ın geçerlilik süresi gibi bilgiler bulunur.
- Kısa süreli (ör. 5–15 dk) ömür
- API çağrılarında Authorization: Bearer ile taşınır
- Sunucu tarafında oturum tutmadan doğrulanabilir
Refresh token ne işe yarar?
Refresh token (yenileme belirteci) access token süresi bittiğinde yeni access token almak için kullanılır. Refresh token daha uzun ömürlüdür ve güvenli saklama/rotasyon gerektirir. En kritik fark: refresh token mutlaka sunucu tarafında izlenebilir olmalıdır. Böylece iptal (revocation), rotasyon ve anomali tespiti yapılabilir.
- Uzun süreli (ör. 7–30 gün) ömür
- Her yenilemede rotasyon uygulanması önerilir
- Veritabanında hash’li olarak saklamak iyi pratiktir
Neden sadece JWT yeterli değildir?
“JWT stateless, o zaman her şeyi içine koyup bitsin” yaklaşımı cazip görünse de pratikte iki problem doğurur: (1) Token ele geçirilirse son kullanma süresine kadar geçerlidir, (2) kullanıcının çıkış yapması veya hesap güvenlik olaylarında token iptali zorlaşır. Refresh token + kısa ömürlü access token kombinasyonu bu riski azaltır.
ASP.NET Core’da JWT doğrulama altyapısını kurmak
Authentication şeması ve token doğrulama parametreleri
JWT doğrulamada en kritik unsur, doğrulama parametrelerini (issuer, audience, signing key, clock skew) netleştirmektir. Özellikle issuer/audience kontrolünü “sonra bakarız” diye bırakmak, token’ın farklı ortamlarda yanlış kabul edilmesine yol açabilir. Ayrıca clock skew toleransını gereksiz yüksek tutmak, süresi dolmuş token’ların kabul edilme aralığını büyütür.
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var jwtSection = builder.Configuration.GetSection("Jwt");
var issuer = jwtSection["Issuer"];
var audience = jwtSection["Audience"];
var key = jwtSection["SigningKey"];
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/secure", () => "ok").RequireAuthorization();
app.Run();Burada önemli olan iki nokta var: (1) SigningKey güçlü ve yönetilebilir olmalı, (2) issuer/audience değerleri ortam bazında tutarlı yönetilmeli. Geliştirme ortamında “her şeyi kabul et” yaklaşımı prod’a sızdığında ciddi risk oluşturur.

Token üretimi: Claims, süreler ve imzalama
JWT içine ne koymalı, ne koymamalı?
JWT’ye yalnızca ihtiyaç duyulan claim’leri koymak gerekir. Çok fazla veri eklemek, token’ı şişirir; hassas veri koymak ise risklidir. Örneğin kullanıcı e-postası çoğu senaryoda gereksizdir; rol/izin bilgisi gerekiyorsa minimal tutulmalıdır. Ayrıca token içine “gizli” sayılabilecek veri konulmamalı; JWT şifreli değil, imzalıdır.
Örnek token üretimi (access + refresh)
Aşağıdaki örnek, access token’ı kısa süreli üretir; refresh token’ı ise rastgele, yüksek entropili bir dize olarak üretip veritabanında hash’li şekilde saklamayı hedefler. Refresh token’ı JWT olarak üretmek de mümkündür; ancak iptal/rotasyon için yine sunucu tarafında izlemek gerekir.
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using System.Text;
public class TokenService
{
private readonly IConfiguration _config;
public TokenService(IConfiguration config)
{
_config = config;
}
public string CreateAccessToken(string userId, IEnumerable<Claim> extraClaims)
{
var jwt = _config.GetSection("Jwt");
var issuer = jwt["Issuer"];
var audience = jwt["Audience"];
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwt["SigningKey"]));
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
claims.AddRange(extraClaims);
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expires = DateTime.UtcNow.AddMinutes(10);
var token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
expires: expires,
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
public string CreateRefreshToken()
{
var bytes = RandomNumberGenerator.GetBytes(64);
return Convert.ToBase64String(bytes);
}
public static string HashToken(string refreshToken)
{
using var sha = SHA256.Create();
var hashed = sha.ComputeHash(Encoding.UTF8.GetBytes(refreshToken));
return Convert.ToBase64String(hashed);
}
}Bu yaklaşımda refresh token’ı ham haliyle veritabanında tutmak yerine hash’leyerek saklamak, veri sızıntısı senaryosunda etkiyi azaltır. Ayrıca access token içinde JTI kullanmak, izleme ve anomali analizinde işe yarar.
Refresh token akışı: rotasyon, iptal ve güvenli saklama
Token rotasyonu neden kritik?
Token rotasyonu, her yenileme isteğinde eski refresh token’ı geçersiz kılıp yenisini üretmektir. Böylece bir refresh token ele geçirilse bile saldırganın “aynı token ile” sürekli yenileme yapması engellenir. Rotasyonla birlikte “reuse detection” (yeniden kullanım tespiti) kurgulanırsa, çalınan token’ın tekrar kullanımı yakalanabilir.
Önerilen yenileme akışı
- Client refresh token ile /auth/refresh çağırır.
- Sunucu refresh token hash’ini bulur; süresini ve durumunu kontrol eder.
- Refresh token geçerliyse: yeni access token + yeni refresh token üretir.
- Eski refresh token “revoked/rotated” olarak işaretlenir.
- Eski token tekrar gelirse: oturumu sonlandır ve risk kaydı üret.
Refresh token’ı nerede saklamalı?
Web uygulamalarında refresh token’ı HttpOnly ve Secure cookie’de tutmak çoğu zaman iyi bir denge sunar. SPA’da localStorage kullanımı XSS riskini büyütür. Mobil uygulamalarda platformun güvenli saklama mekanizmaları tercih edilmelidir. Hangi yolu seçerseniz seçin, refresh token’ın “kolay erişilebilir” bir yerde durmaması esastır.
Ayrıca cookie kullanıyorsanız CSRF riskini değerlendirin: refresh endpoint’i için SameSite ayarları, anti-forgery stratejileri ve origin kontrolleri birlikte düşünülmelidir. “Bearer + cookie” hibrit tasarımlarda sınırları net çizmek önemlidir.

Yetkilendirme: role, claim ve policy ile doğru sınırlar
Role tabanlı yetkilendirme ne zaman yeterli?
Basit senaryolarda role tabanlı yaklaşım iş görür: Admin, User gibi rollerle endpoint erişimi sınırlandırılabilir. Ancak kurumsal API’lerde genellikle “iş yetkisi” role’dan daha ince ayrıntılar içerir. Bu noktada claim tabanlı yetkilendirme devreye girer.
Policy tabanlı yaklaşımın avantajları
Policy’ler, yetkilendirme kurallarını tek bir yerde tanımlamayı sağlar. Örneğin “invoices:read” iznine sahip kullanıcılar fatura listeleyebilsin gibi. Bu model, mikroservislerde ve farklı client’ların aynı API’yi kullandığı ortamlarda daha sürdürülebilirdir. Ayrıca denetim (audit) süreçleri için de daha nettir.
using Microsoft.AspNetCore.Authorization;
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("CanReadInvoices", policy =>
policy.RequireClaim("perm", "invoices:read"));
options.AddPolicy("CanManageUsers", policy =>
policy.RequireAssertion(ctx =>
ctx.User.HasClaim("role", "admin") ||
ctx.User.HasClaim("perm", "users:write")));
});
app.MapGet("/invoices", [Authorize(Policy = "CanReadInvoices")] () => Results.Ok(new[] { "INV-001" }));
app.MapPost("/users", [Authorize(Policy = "CanManageUsers")] () => Results.Ok("created"));Burada “perm” claim’i bir secondary keyword olan claim tabanlı yetkilendirme yaklaşımını temsil ediyor. Claim isimlerini standartlaştırmak (ör. “perm”, “scope”) ve kimlik sağlayıcıyla uyumlu yönetmek ileride ciddi zaman kazandırır.
Güvenli API tasarımı: pratik sertleştirme adımları
HTTPS, CORS ve güvenli başlıklar
JWT taşıyan her çağrı TLS üzerinden yapılmalı. HTTPS zorunluluğu yalnızca “best practice” değil, pratikte token ele geçirilmesini zorlaştıran temel bariyerdir. CORS tarafında ise sadece ihtiyaç duyulan origin’leri izinli hale getirmek gerekir. “*” ile geçiştirmek, özellikle tarayıcı tabanlı istemcilerde riskli sonuçlar doğurabilir.
Rate limiting ve brute force savunması
Kimlik doğrulama endpoint’leri (login, refresh) saldırıların en sık hedeflediği noktalardır. Rate limiting, IP/cihaz bazlı eşiklerle brute force denemelerini sınırlar. Ayrıca hatalı giriş sayısına göre gecikme (progressive delay) eklemek, otomasyon saldırılarını pahalı hale getirir.
Logging, denetim izi ve anomali tespiti
Token doğrulama hataları, refresh token reuse denemeleri, beklenmeyen cihaz değişimleri gibi olaylar anlamlı şekilde loglanmalıdır. Ancak log’a token’ın kendisini yazmak ciddi hatadır. Bunun yerine JTI, kullanıcı id, istek kaynağı ve zaman bilgileriyle denetim izi tutulabilir. Bu pratik, “güvenli API tasarımı” hedefinin önemli bir parçasıdır.
Anahtar yönetimi ve rotasyon: sürdürülebilir güvenlik
Signing key nasıl yönetilmeli?
Simetrik anahtar (HMAC) hızlıdır, ancak anahtarın paylaşımı ve rotasyonu süreç disiplinine bağlıdır. Asimetrik anahtar (RSA/ECDSA) kullanırsanız, doğrulama için public key dağıtımı kolaylaşır; özellikle çoklu servis doğrulama senaryolarında avantaj sağlar. Hangi yaklaşımı seçerseniz seçin, anahtarları kod içine gömmek yerine güvenli bir gizli yönetim sistemiyle yönetmek gerekir.
Key rotation planı ve geri uyumluluk
Anahtar rotasyonu planlanırken eski token’ların bir süre daha doğrulanabilmesi gerekebilir. Bu nedenle “aktif” ve “eski” anahtarların bir süre birlikte tutulduğu bir model tercih edilir. JWT header içindeki “kid” (key id) alanı, doğru anahtarla doğrulama için kullanılabilir. Böylece rotasyon sırasında kesinti yaşanmaz.
Örnek uçtan uca akış: login, refresh ve logout
Login endpoint’inde dikkat edilmesi gerekenler
Login, yalnızca doğru kullanıcı adı/şifre kontrolü değildir: hesap kilitleme, parola deneme sayısı, MFA, cihaz izi gibi unsurların devreye alındığı katmandır. Minimum seviyede bile, başarısız denemeleri sınırlamak ve hataları kullanıcıya “aşırı bilgi” vermeden döndürmek önemlidir.
Refresh endpoint’ini ayrı bir güvenlik katmanı olarak düşünmek
Refresh endpoint’i, access token üretmenin “arka kapısı” gibidir; bu yüzden ekstra korumaya ihtiyaç duyar. Örneğin sadece belirli client türlerinin yenileme yapmasına izin vermek, refresh token’ı cookie’den alıp origin kontrolü yapmak ve rotasyonla reuse tespiti uygulamak güçlü bir kombinasyondur.
Logout ve token iptali
Stateless dünyada “logout” kafa karıştırabilir. Pratikte logout, refresh token’ın iptal edilmesi ve varsa aktif refresh token zincirinin sonlandırılmasıdır. Access token zaten kısa ömürlü olduğu için, logout sonrası en fazla kısa bir süre geçerliliğini koruyabilir; bu, tasarımın bilinçli bir trade-off’udur. Riskli senaryolarda access token ömrünü daha da kısaltmak veya JTI tabanlı denylist yaklaşımını değerlendirmek gerekebilir.
Sık yapılan hatalar ve kısa kontrol listesi
Aşağıdaki maddeler, sahada en sık görülen sorunları hızlıca yakalamanıza yardımcı olur:
- Issuer/Audience doğrulaması kapalı bırakılıyor.
- Access token çok uzun ömürlü ayarlanıyor.
- Refresh token rotasyonu uygulanmıyor.
- Refresh token düz metin saklanıyor; hash kullanılmıyor.
- Token’lar log’lara yazılıyor veya hata mesajları fazla detay veriyor.
- CORS “herkese açık” bırakılıyor; refresh endpoint’i ekstra korunmuyor.
Bu kontrol listesiyle ilerlerseniz, JWT tabanlı kimlik doğrulamada temel güvenlik beklentilerini karşılayan bir yapı kurarsınız. Sonrasında ihtiyaçlarınıza göre OAuth2/OpenID Connect entegrasyonlarını, MFA ve cihaz yönetimi gibi katmanları ekleyerek olgunlaştırabilirsiniz.
Özetle: Kısa ömürlü JWT access token, izlenebilir ve rotasyonlu refresh token, policy tabanlı yetkilendirme ve sertleştirilmiş API yüzeyi birlikte çalıştığında sürdürülebilir bir güvenlik mimarisi ortaya çıkar. Bu mimariyi kurarken kararlarınızı dokümante etmek ve log/denetim izini doğru tasarlamak, ileride yaşanacak olaylarda büyük fark yaratır.


