Ledbim.Security 1.2.0

dotnet add package Ledbim.Security --version 1.2.0
                    
NuGet\Install-Package Ledbim.Security -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Ledbim.Security" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ledbim.Security" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Ledbim.Security" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Ledbim.Security --version 1.2.0
                    
#r "nuget: Ledbim.Security, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Ledbim.Security@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Ledbim.Security&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Ledbim.Security&version=1.2.0
                    
Install as a Cake Tool

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:

  1. Hırsız eski (revoked) token ile RotateAsync() çağırır
  2. Sistem token'in zaten revoked olduğunu görür
  3. O token'in tüm ailesi anında revoke edilir (FamilyCompromised)
  4. 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 ihlalinde CryptographicException fı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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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