.NET Core API
.NET Core API — Bölüm 2: JWT ile Authentication ve Güvenlik
API'mize JWT tabanlı kimlik doğrulama ve yetkilendirme ekliyoruz. Token üretme, Authorize attribute'u ile endpoint koruma, rol tabanlı kontrol, current user erişimi ve refresh token pattern'i — güvenli bir API için tüm ipuçları.
22 Nisan 2026
6 dk okuma
22
0
IDE tarafinda: Visual Studio tarafinda Identity scaffolder'i calistirinca sayfalar hazir geliyor, fakat API icin JWT'ye cevirmemiz gerek. Tools > Manage User Secrets menusunden JWT key'i guvenli yere koyuyoruz.
Merhaba arkadaşlar, .NET Core API serisinin ikinci bölümüne hoş geldiniz. Önceki bölümde CRUD API'mizi yazmıştık ama tüm endpoint'ler açıktaydı — herkes her şeyi yapabiliyordu. Bu makalede JWT (JSON Web Token) ile API'mize profesyonel bir güvenlik katmanı ekleyeceğiz. Kullanıcı girişi, token üretimi, rol tabanlı yetkilendirme ve best practice'ler — hepsi örnekli anlatım.JWT Nedir, Neden JWT?
JWT (jot diye okunuyor), kullanıcının kimliğini ve iddialarını taşıyan imzalı bir token. Üç parçadan oluşur, noktayla ayrılır:
header.payload.signature. Her parça Base64Url ile encode edilmiş JSON.Neden popüler:
- Stateless: Sunucuda session tutmaya gerek yok, token her şeyi taşır. Horizontal scaling rahatlar.
- Self-contained: Kullanıcı ID, rol, expire tarihi token'ın içinde.
- İmzalı: Secret key ile imzalandığı için manipüle edilemez.
- Cross-domain: Cookie gibi same-origin kısıtı yok, mobile app'lerden de rahatça kullanılır.
Ama dikkat edilecek şeyler de var:
- Token payload şifreli değil sadece imzalı — hassas veri koymayın.
- Yayınlanan bir token'ı "iptal etmek" zordur (revoke list tutmak gerek).
- Secret key sızarsa oyun bitti — güvenli saklayın.
Gerekli Paketleri Yükleyelim
Projemize şu paketleri ekliyoruz:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.IdentityModel.Tokens
dotnet add package System.IdentityModel.Tokens.Jwt
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
JWT Ayarlarını Konfigüre Edelimappsettings.json içine JWT ayarlarını ekliyoruz:{
"Jwt": {
"Key": "CokCokUzunVeGizliBirSecretKeyBunuSizProduktionAsla-BuradaTutmayin-En-Az-32-Karakter",
"Issuer": "https://api.semgoksu.com",
"Audience": "https://www.semgoksu.com",
"ExpireMinutes": 60
}
}
Çok önemli: Secret key'i kesinlikle git'e commit etmeyin. Development için appsettings, production için Azure Key Vault, AWS Secrets Manager veya en azından environment variable kullanın.Program.cs'te Authentication Kurulumu
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var jwtKey = builder.Configuration["Jwt:Key"]!;
var jwtIssuer = builder.Configuration["Jwt:Issuer"]!;
var jwtAudience = builder.Configuration["Jwt:Audience"]!;
var keyBytes = Encoding.UTF8.GetBytes(jwtKey);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtIssuer,
ValidAudience = jwtAudience,
IssuerSigningKey = new SymmetricSecurityKey(keyBytes),
ClockSkew = TimeSpan.Zero // varsayılan 5 dk tolerans yerine sıkı olalım
};
});
builder.Services.AddAuthorization();
// ... diğer servisler
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Middleware sırası çok kritik: UseAuthentication mutlaka UseAuthorization'dan önce, ikisi de MapControllers'dan önce gelmeli.Token Üreten Servis
Kullanıcı giriş yaptığında bir JWT üretmemiz gerekiyor. Ayrı bir servis olarak yazalım:
public interface ITokenService
{
string TokenUret(int kullaniciId, string kullaniciAdi, string email, IEnumerable<string> roller);
}
public class JwtTokenService(IConfiguration config) : ITokenService
{
public string TokenUret(int kullaniciId, string kullaniciAdi, string email, IEnumerable<string> roller)
{
var key = config["Jwt:Key"]!;
var issuer = config["Jwt:Issuer"]!;
var audience = config["Jwt:Audience"]!;
var expireMin = int.Parse(config["Jwt:ExpireMinutes"] ?? "60");
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Sub, kullaniciId.ToString()),
new(JwtRegisteredClaimNames.UniqueName, kullaniciAdi),
new(JwtRegisteredClaimNames.Email, email),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
claims.AddRange(roller.Select(r => new Claim(ClaimTypes.Role, r)));
var keyBytes = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var creds = new SigningCredentials(keyBytes, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddMinutes(expireMin),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
Program.cs'te register:builder.Services.AddScoped<ITokenService, JwtTokenService>();
Login Endpoint'iKullanıcının kullanıcı adı + şifre göndereceği, karşılığında token alacağı bir endpoint:
public record LoginRequest(string KullaniciAdi, string Sifre);
public record LoginResponse(string Token, DateTime GecerlilikSuresi, string KullaniciAdi);
[ApiController]
[Route("api/[controller]")]
public class AuthController(IKullaniciServisi kullaniciServisi, ITokenService tokenService) : ControllerBase
{
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest istek)
{
var kullanici = await kullaniciServisi.DogrulaAsync(istek.KullaniciAdi, istek.Sifre);
if (kullanici is null)
return Unauthorized(new { hata = "Kullanıcı adı veya şifre hatalı" });
var roller = await kullaniciServisi.RolleriGetirAsync(kullanici.Id);
var token = tokenService.TokenUret(kullanici.Id, kullanici.KullaniciAdi, kullanici.Email, roller);
var expire = DateTime.UtcNow.AddMinutes(60);
return Ok(new LoginResponse(token, expire, kullanici.KullaniciAdi));
}
}
Şifre doğrulama tarafında asla plaintext şifre saklanmaz. BCrypt, Argon2, veya .NET'in kendi PasswordHasher'ı kullanın:// PasswordHasher kullanım örneği
var hasher = new PasswordHasher<Kullanici>();
// kayıt sırasında:
var hash = hasher.HashPassword(kullanici, "planTextSifre");
// doğrulama sırasında:
var result = hasher.VerifyHashedPassword(kullanici, kullanici.SifreHash, "girilenSifre");
if (result == PasswordVerificationResult.Success) { /* OK */ }
Endpoint'leri KoruyalımArtık controller'larımızda
[Authorize] attribute'u kullanarak koruma sağlayabiliyoruz:[ApiController]
[Route("api/[controller]")]
public class YazilarController : ControllerBase
{
[HttpGet] // herkes okuyabilir
public IActionResult Tumu() => Ok(...);
[HttpGet("{id}")] // herkes okuyabilir
public IActionResult Getir(int id) => Ok(...);
[Authorize] // giriş yapmış herkes yazabilir
[HttpPost]
public IActionResult Ekle([FromBody] Yazi yeni) => Created(...);
[Authorize(Roles = "Admin,Editor")] // sadece admin/editor
[HttpPut("{id}")]
public IActionResult Guncelle(int id, [FromBody] Yazi y) => NoContent();
[Authorize(Roles = "Admin")] // sadece admin
[HttpDelete("{id}")]
public IActionResult Sil(int id) => NoContent();
}
İstemci artık bu endpoint'leri çağırırken HTTP header'a token eklemeli:Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn...
Current User'a ErişmekAuthorize edilmiş bir endpoint'te kullanıcı bilgisine
User propertisi üzerinden ulaşabiliyoruz:[Authorize]
[HttpGet("benim")]
public IActionResult Benim()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier)
?? User.FindFirstValue(JwtRegisteredClaimNames.Sub);
var email = User.FindFirstValue(ClaimTypes.Email)
?? User.FindFirstValue(JwtRegisteredClaimNames.Email);
var roller = User.FindAll(ClaimTypes.Role).Select(c => c.Value);
return Ok(new { userId, email, roller });
}
Refresh Token PatternJWT'nin bir dezavantajı: çıkarıldıktan sonra expire olana kadar geçerlidir. Kullanıcının oturumunu uzatmak için refresh token pattern kullanırız:
- Access token: Kısa ömürlü (15-60 dk), API istekleri için.
- Refresh token: Uzun ömürlü (7-30 gün), DB'de saklı, access token süresi dolunca yenileri alınır.
public record RefreshRequest(string AccessToken, string RefreshToken);
[HttpPost("refresh")]
public async Task<IActionResult> Refresh([FromBody] RefreshRequest req)
{
// 1. Expired access token'ı validate et (signature'ı hâlâ kontrol ediyoruz)
// 2. Refresh token'ı DB'den bul, geçerli mi kontrol et
// 3. Yeni bir access token + refresh token üret
// 4. Eski refresh token'ı revoke et (rotation)
// 5. Yenilerini dön
...
}
Detayları uzun sürer ama production'da mutlaka bu pattern'i kullanın.Yaygın Güvenlik Hataları
Yıllar içinde gördüğüm en sık hatalar:
- Secret key'i git'e commit etmek: Public repo'ysa bir saat içinde birileri bot ile bulur.
- HTTPS kullanmamak: Token man-in-the-middle ile kapılır. Development'ta bile HTTPS zorunlu.
- Token'ı localStorage'da tutmak: XSS riskine açık. httpOnly cookie tercih edin veya en azından refresh'i cookie'de tutun.
- Expire süresini çok uzun vermek: 60 dk üst sınır tutun, refresh ile yenileyin.
- Validation atlama:
ValidateLifetime=false asla production'da olmamalı.- Rol kontrolü unutmak: Sadece
[Authorize] yetmez, işlem izninin rolünü de kontrol edin.Özet
JWT ile API'mize kimlik doğrulama ve yetkilendirme kattık. Token üretme, Authorize attribute'u ile endpoint koruma, rol tabanlı kontrol, current user'a erişme, ve refresh token pattern'ini gördük.
Bir sonraki ve son bölümde API'mizi geliştiriciler için dokümante ediyor olacağız: Swagger/OpenAPI ile otomatik API dokümantasyonu, "Try it out" ile canlı deneme, ve production için ideal konfigürasyon. Bol kolay gelsin, yorumlarınızı yazın...
Yorumlar (0)
Henüz yorum yok. İlk yorumu sen yap!