Ledbim.Security
1.2.0
dotnet add package Ledbim.Security --version 1.2.0
NuGet\Install-Package Ledbim.Security -Version 1.2.0
<PackageReference Include="Ledbim.Security" Version="1.2.0" />
<PackageVersion Include="Ledbim.Security" Version="1.2.0" />
<PackageReference Include="Ledbim.Security" />
paket add Ledbim.Security --version 1.2.0
#r "nuget: Ledbim.Security, 1.2.0"
#:package Ledbim.Security@1.2.0
#addin nuget:?package=Ledbim.Security&version=1.2.0
#tool nuget:?package=Ledbim.Security&version=1.2.0
Ledbim.Security
<p align="center"> <img src="ledbim_security_logo.png" alt="Ledbim.Security" width="120" /> </p>
<p align="center"> <strong>JWT · BasicAuth · Refresh Token · AES-GCM · PBKDF2</strong><br/> Ledbim ekosistemi için üretim hazır kimlik doğrulama ve şifreleme altyapısı. </p>
Kapsam
Ledbim.Security paketi şu bileşenleri sağlar:
| Bileşen | Açıklama |
|---|---|
| JWT Hybrid Scheme | JWT Bearer ve BasicAuth'u tek scheme altında birleştirir — Authorization header'ına göre otomatik seçim |
| Refresh Token Servisi | Rotation, aile takibi ve token hırsızlığı tespiti dahil tam yaşam döngüsü yönetimi |
| AES-256-GCM | Kimlik doğrulama etiketli simetrik şifreleme — veri bütünlüğü garantili |
| PBKDF2-SHA512 | Parola hash'leme ve doğrulama — 100.000 iterasyon, timing-safe karşılaştırma |
| BasicAuth Handler | RFC 2617 uyumlu, pluggable validator desteği |
Kurulum
dotnet add package Ledbim.Security
Bağımlılıklar
Microsoft.AspNetCore.Authentication.JwtBearer
Hızlı Başlangıç
1. appsettings.json
{
"JWT": {
"AccessTokenSecurityKey": "en-az-32-karakter-güvenli-bir-anahtar",
"Issuer": "MyApp",
"Audience": "MyAppClients",
"AccessTokenExpiration": 60,
"RefreshTokenExpiration": 168
}
}
| Alan | Tip | Açıklama |
|---|---|---|
AccessTokenSecurityKey |
string | HMAC-SHA256 imzalama anahtarı (min. 32 karakter) |
Issuer |
string | Token üreticisi — doğrulama için kullanılır |
Audience |
string | Token alıcısı — doğrulama için kullanılır |
AccessTokenExpiration |
int | Access token ömrü (dakika) |
RefreshTokenExpiration |
int | Refresh token ömrü (saat) |
2. Program.cs
// JWT + BasicAuth hybrid scheme
builder.Services.AddJwtAuthentication(builder.Configuration);
// Refresh token (opsiyonel — IRefreshTokenStore implementasyonu gerektirir)
builder.Services.AddRefreshTokenService<YourRefreshTokenRepository>();
// Middleware
app.UseAuthentication();
app.UseAuthorization();
Hybrid Authentication Scheme
AddJwtAuthentication() çağrıldığında üç scheme birlikte kurulur:
İstek gelir
│
▼
Authorization header var mı?
│
├── "Basic ..." ile başlıyorsa ──► BasicAuthenticationHandler
│ └── IBasicAuthCredentialValidator.ValidateAsync()
│
└── Diğer durumlarda ──────────► JwtBearerHandler
└── HMAC-SHA256 imza + issuer + audience + lifetime doğrulaması
JWT Bearer
| Validasyon | Değer |
|---|---|
| Algoritma | HMAC-SHA256 |
| İmza doğrulaması | Zorunlu |
| Issuer kontrolü | Zorunlu |
| Audience kontrolü | Zorunlu |
| Lifetime kontrolü | Zorunlu |
| Clock skew toleransı | 5 dakika |
| JTI (JWT ID) | Otomatik eklenir, yoksa Guid.NewGuid() |
SignalR / WebSocket desteği: /hubs/ ile başlayan bağlantılar için ?access_token=... query string'ten token okunur.
BasicAuth
Uygulama tarafından IBasicAuthCredentialValidator implement edilmelidir:
public class MyCredentialValidator(IUserService userService) : IBasicAuthCredentialValidator
{
public async Task<BasicAuthValidationResult?> ValidateAsync(
string username, string password, CancellationToken ct)
{
var user = await userService.ValidateAsync(username, password, ct);
if (user is null) return null;
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
new(ClaimTypes.Name, user.Email),
new(ClaimTypes.Role, user.Role)
};
return new BasicAuthValidationResult(user.Email, claims);
}
}
// Program.cs
builder.Services.AddScoped<IBasicAuthCredentialValidator, MyCredentialValidator>();
JWT Token Üretimi
// Constructor injection
IJwtTokenGenerator jwtGenerator
// Access token üret
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
new("UserName", user.Email),
new(ClaimTypes.Role, user.Role)
};
var (accessToken, expiresAt) = jwtGenerator.GenerateAccessToken(claims);
IJwtTokenGenerator, AddJwtAuthentication() ile otomatik olarak DI'ya kaydedilir. Ayrıca eklemek gerekmez.
Refresh Token
Genel Bakış
Refresh token sistemi stateful'dur — her token SHA-256 hash'i veritabanında tutulur. Sunucuya asla raw token depolanmaz.
İlk giriş
│
▼
CreateAsync() → 64-byte raw token → Base64 → istemciye gönderilir
→ SHA-256 hash → veritabanına kaydedilir (FamilyId atanır)
Token yenileme
│
▼
RotateAsync() → Eski token hash veritabanında aranır
→ Zaten revoked? → TÜM aile revoke edilir (hırsızlık tespiti!)
→ Süresi dolmuş? → Fail döner
→ Geçerli token → Eski token "Rotated" olarak revoke edilir
→ Yeni raw token + access token döner
Çıkış
│
├── RevokeAsync() → Tek cihazdan çıkış
└── RevokeAllAsync() → Tüm cihazlardan çıkış
IRefreshTokenStore Implementasyonu
public interface IRefreshTokenStore
{
Task SaveAsync(RefreshTokenEntity token, CancellationToken ct = default);
Task<RefreshTokenEntity?> GetByHashAsync(string tokenHash, CancellationToken ct = default);
Task UpdateAsync(RefreshTokenEntity token, CancellationToken ct = default);
Task RevokeByFamilyIdAsync(Guid familyId, string reason, CancellationToken ct = default);
Task RevokeAllByUserIdAsync(string userId, string reason, CancellationToken ct = default);
}
| Metod | Açıklama |
|---|---|
SaveAsync |
Yeni oluşturulan token entity'sini persist eder |
GetByHashAsync |
Token hash'e göre entity'yi getirir — rotation ve reuse detection için kritik |
UpdateAsync |
Revoke/rotation sonrası entity state günceller |
RevokeByFamilyIdAsync |
Aynı giriş oturumundan türemiş tüm tokenleri iptal eder |
RevokeAllByUserIdAsync |
Bir kullanıcının tüm cihazlardaki tokenlerini iptal eder |
Önerilen veritabanı indeksleri:
CREATE UNIQUE INDEX IX_RefreshTokens_TokenHash ON RefreshTokens (TokenHash);
CREATE INDEX IX_RefreshTokens_UserId_IsRevoked ON RefreshTokens (UserId, IsRevoked);
CREATE INDEX IX_RefreshTokens_FamilyId ON RefreshTokens (FamilyId);
RefreshTokenEntity Alanları
public class RefreshTokenEntity : BaseEntity<Guid>
{
public string TokenHash { get; set; } // SHA-256 hex, 64 karakter
public Guid FamilyId { get; set; } // Aynı oturumun token ailesi
public string UserId { get; set; }
public DateTime ExpiresAt { get; set; }
public bool IsRevoked { get; set; }
public DateTime? RevokedAt { get; set; }
public string? RevokedReason { get; set; } // "Rotated" | "Logout" | "FamilyCompromised"
public string? ReplacedByTokenHash { get; set; } // Rotation zinciri takibi
public string? IpAddress { get; set; }
public string? DeviceInfo { get; set; }
}
Kullanım Örneği
// Login handler'ında
public async Task<Result<LoginResponse>> Handle(LoginCommand request, CancellationToken ct)
{
var user = await ValidateUserAsync(request.Email, request.Password);
var ipAddress = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString();
var deviceInfo = httpContextAccessor.HttpContext?.Request.Headers["User-Agent"].ToString();
var (rawToken, tokenEntity) = await refreshTokenService.CreateAsync(
user.Id.ToString(), ipAddress, deviceInfo, ct);
await uow.RefreshTokenRepository.AddAsync(tokenEntity);
var claims = new List<Claim> { new(ClaimTypes.NameIdentifier, user.Id.ToString()) };
var (accessToken, expiresAt) = jwtGenerator.GenerateAccessToken(claims);
return Result<LoginResponse>.Success(ResultType.Success, new LoginResponse(
AccessToken: accessToken,
RefreshToken: rawToken,
ExpiresAt: expiresAt));
}
// Refresh handler'ında
var result = await refreshTokenService.RotateAsync(
request.RefreshToken, ipAddress, deviceInfo, ct);
if (!result.IsSuccess)
return Result.Fail(result.ResponseType, result.Message);
return Result<TokenResponse>.Success(ResultType.Success, new TokenResponse(
result.Data!.AccessToken,
result.Data.RefreshToken));
Token Hırsızlığı Tespiti (Theft Detection)
Bir refresh token çalınıp iki kez kullanılmaya çalışılırsa:
- Hırsız eski (revoked) token ile
RotateAsync()çağırır - Sistem token'in zaten revoked olduğunu görür
- O token'in tüm ailesi anında revoke edilir (
FamilyCompromised) - Gerçek kullanıcı bir sonraki istekte hata alır ve yeniden giriş yapar
Bu yaklaşım RFC 6749 Section 10.4 önerisiyle uyumludur.
AES-256-GCM Şifreleme
Hassas veri şifreleme için kullanılır (örneğin refresh token'i cookie'de saklarken veya veritabanında hassas alanları şifrelerken).
Özellikler
| Parametre | Değer |
|---|---|
| Algoritma | AES-256-GCM |
| Anahtar boyutu | 256-bit (32 byte) |
| Nonce boyutu | 96-bit (12 byte) — her şifrelemede yeni, rastgele |
| Auth tag | 128-bit (16 byte) — bütünlük + özgünlük garantisi |
| Payload formatı | [1B version] + [12B nonce] + [16B auth tag] + [ciphertext] → Base64 |
Uygulama Başlangıcında Yapılandırma
// Program.cs veya startup
var key = builder.Configuration["AES:Key"]; // 32 byte → Base64
AesGcmCrypto.InitializeFromBase64(key);
Anahtar oluşturmak için:
var newKey = AesGcmCrypto.GenerateKeyBase64(); // Güvenli random 256-bit key
Kullanım
// Global key ile (InitializeFromBase64 çağrıldıysa)
string encrypted = AesGcmCrypto.Encrypt("hassas veri");
string decrypted = AesGcmCrypto.Decrypt(encrypted);
// Hata durumunu güvenli yönetmek için
if (AesGcmCrypto.TryDecrypt(encrypted, out var plaintext))
{
// plaintext kullan
}
// Per-call key ile
byte[] key = Convert.FromBase64String(config["AES:Key"]);
string encrypted = AesGcmCrypto.Encrypt("hassas veri", key);
string decrypted = AesGcmCrypto.Decrypt(encrypted, key);
Not: AES-GCM şifreleme, manipülasyon girişimlerini auth tag aracılığıyla tespit eder.
Decrypt()bütünlük ihlalindeCryptographicExceptionfırlatır.
Parola Hash'leme (PBKDF2)
// Injection
IHashProperty hasher
// Hash'leme (kullanıcı kaydında)
string hash = hasher.Hash(password); // "{hash_hex}-{salt_hex}" formatında döner
// Doğrulama (kullanıcı girişinde)
bool isValid = hasher.Verify(password, storedHash); // Timing-safe karşılaştırma
Güvenlik Parametreleri
| Parametre | Değer | Açıklama |
|---|---|---|
| Algoritma | PBKDF2-SHA512 | NIST SP 800-132 uyumlu |
| Iterasyon | 100.000 | Brute-force direnci |
| Salt | 16 byte (128-bit) | Her hash için rastgele |
| Hash boyutu | 32 byte (256-bit) | Çıktı uzunluğu |
| Karşılaştırma | Fixed-time | Timing saldırısına karşı |
DI kaydı:
builder.Services.AddScoped<IHashProperty, HashProperty>();
Tam DI Kayıt Örneği
var builder = WebApplication.CreateBuilder(args);
// JWT + BasicAuth (zorunlu)
builder.Services.AddJwtAuthentication(builder.Configuration);
// BasicAuth validator (BasicAuth kullanılıyorsa zorunlu)
builder.Services.AddScoped<IBasicAuthCredentialValidator, MyCredentialValidator>();
// Refresh token (opsiyonel)
builder.Services.AddRefreshTokenService<MyRefreshTokenRepository>();
// Parola hash (opsiyonel)
builder.Services.AddScoped<IHashProperty, HashProperty>();
// AES-GCM (opsiyonel — global key yöntemi tercih edilirse)
AesGcmCrypto.InitializeFromBase64(builder.Configuration["AES:Key"]!);
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
Mimari Notlar
- Stateful refresh token: Access token stateless, refresh token stateful. Bu tasarım token iptalini anında mümkün kılar.
- SHA-256 token storage: Veritabanı sızdırılsa bile raw token'ler ele geçirilemez — tek yönlü hash.
- Family-based revocation: Bir token çalındığında sadece o token değil, aynı oturumun tüm türevleri iptal edilir.
- JTI otomatik eklenir:
GenerateAccessToken()çağrısında JTI claim yoksa otomatik olarak eklenir. RequireHttpsMetadata = false: Geliştirme ortamı içindir. Üretimde HTTPS zorunludur.
Lisans
Bu paket Ledbim Bilişim tarafından geliştirilmektedir. Ticari kullanım için lisans bilgisi için iletişime geçiniz.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Ledbim.Core (>= 1.2.0)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.2.0 | 174 | 3/28/2026 |