Skip to main content
BuildingBlocks.Otp, tek kullanımlık parola (OTP) üretimi ve doğrulaması için altyapı sağlar. Kod plaintext saklanmaz; yalnızca HMAC-SHA256 özeti tutulur ve doğrulama stateless karşılaştırma ile yapılır. Paket aynı zamanda rate-limit politikalarını (throttle, günlük limit, deneme sayısı) konfigürasyon olarak taşır.
OTP’nin User aggregate ile entegrasyonu (UserOtpChallenge, AddOtpChallenge/VerifyOtp) ve SMS gönderimi (OtpSmsService, NetGSM) için bkz. Domain ve SMS servisi. Bu sayfa paket arayüzlerine odaklanır.

IOtpService

Üç yardımcı servisi (generator + hash + formatter) tek fasad altında toplayan orchestrator.
MetotİmzaAmaç
GenerateCodeOtpCode GenerateCode()CodeLength uzunluğunda rastgele numerik kod üretir
Hashstring Hash(OtpCode code)HMAC-SHA256 ile Base64 özet üretir (saklanan değer)
CreateLoginSmsstring CreateLoginSms(OtpCode code)LoginSmsTemplate’teki {code}’u gerçek kodla doldurur

Alt servisler

Arayüzİmzaİmplementasyon
IOtpCodeGeneratorOtpCode GenerateNumericCode(int length)NumericOtpCodeGenerator
IOtpHashServicestring Hash(OtpCode code) · bool Verify(OtpCode code, string hash)HmacOtpHashService (HMAC-SHA256)
IOtpMessageFormatterstring Format(string template, OtpCode code)DefaultOtpMessageFormatter

OtpCode

sealed value object. Kod yalnızca rakamlardan oluşur; loglama güvenlidirToString() her zaman "******" döner, gerçek değere sadece Reveal() ile erişilir.
var code = OtpCode.Create("482913");
_logger.LogInformation("OTP: {Code}", code);   // → "OTP: ******"
string actual = code.Reveal();                  // → "482913" (yalnızca hash/SMS için)
ÜyeDavranış
static OtpCode Create(string)Boş veya rakam-dışı değerde ArgumentException fırlatır
string Reveal()Gerçek kodu döndürür (yalnızca gerektiğinde)
override string ToString()Daima "******" (logging-safe)
Kodu asla Reveal() çıktısıyla loglamayın. Hash daima HMAC-SHA256 + salt ile üretilir; karşılaştırma OtpSecurity.FixedTimeEqualsBase64 ile sabit-zamanlı yapılarak timing attack engellenir.

Konfigürasyon — OtpOptions

Services:Otp bölümüne bind edilir.
ÜyeTipVarsayılanAçıklama
CodeLengthint6Koddaki rakam sayısı
ExpireMinutesint3Kod geçerlilik süresi
ThrottleSecondsint60İki istek (resend) arası minimum süre
MaxRequestPerDayint20Günlük maksimum OTP isteği
MaxVerifyAttemptint5Tek kod için maksimum doğrulama denemesi
MaxFailedVerifyPerDayint50Günlük maksimum başarısız deneme
ChallengeRetentionMinutesint15Challenge state TTL’i
AllowResendWhileActiveboolfalseAktif kod varken yeniden gönderime izin
HashSecretSaltstringHMAC anahtarı (gizli)
LoginSmsTemplatestring"{code} giriş doğrulama kodunuzdur."SMS metni

DI kaydı — AddOtp

public static IServiceCollection AddOtp(this IServiceCollection services, IConfiguration configuration)
{
    services.Configure<OtpOptions>(configuration.GetSection("Services:Otp"));

    services.AddSingleton<IOtpCodeGenerator, NumericOtpCodeGenerator>();
    services.AddSingleton<IOtpHashService, HmacOtpHashService>();
    services.AddSingleton<IOtpMessageFormatter, DefaultOtpMessageFormatter>();
    services.AddSingleton<IOtpService, OtpService>();   // orchestrator

    return services;
}
{
  "Services": {
    "Otp": {
      "CodeLength": 6,
      "ExpireMinutes": 3,
      "ThrottleSeconds": 60,
      "MaxRequestPerDay": 20,
      "MaxVerifyAttempt": 5,
      "MaxFailedVerifyPerDay": 50,
      "ChallengeRetentionMinutes": 15,
      "AllowResendWhileActive": false,
      "HashSecretSalt": "min-32-karakter-gizli-salt",
      "LoginSmsTemplate": "{code} giriş doğrulama kodunuzdur."
    }
  }
}

Kullanım — stateless üretim ve doğrulama

OTP plaintext olarak tutulmaz. Üretimde sadece hash saklanır; doğrulamada girilen kodun hash’i saklanan hash ile karşılaştırılır.
public class OtpLoginService(IOtpService otp, IOtpHashService hasher, IDistributedCache cache)
{
    // 1) Üret → SMS gönder → hash'i sakla (kodun kendisini DEĞİL)
    public async Task RequestAsync(string phone)
    {
        OtpCode code = otp.GenerateCode();
        string hash  = otp.Hash(code);                 // HMAC-SHA256 + salt
        string sms   = otp.CreateLoginSms(code);       // "482913 giriş doğrulama kodunuzdur."

        await cache.SetStringAsync($"otp:{phone}", hash,
            new() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(3) });

        await _smsService.SendAsync(phone, sms);       // OtpSmsService → NetGSM
    }

    // 2) Doğrula — girilen kodu hash'le, saklanan hash ile sabit-zamanlı karşılaştır
    public async Task<bool> VerifyAsync(string phone, string entered)
    {
        string? savedHash = await cache.GetStringAsync($"otp:{phone}");
        if (savedHash is null) return false;           // süresi dolmuş / hiç istenmemiş

        OtpCode code = OtpCode.Create(entered);
        return hasher.Verify(code, savedHash);         // HMAC eşitliği (FixedTimeEquals)
    }
}
Domain tarafında bu mantık User.AddOtpChallenge / User.VerifyOtp ile aggregate içine alınır; challenge state’i UserOtpChallenge child entity’sinde tutulur ve UserOtpGeneratedDomainEvent ile SMS/Email handler’larına yayılır.

İlgili

SMS servisi

NetGSM üzerinden OTP SMS gönderimi.

OTP akışı

Uçtan uca giriş + doğrulama senaryosu.

Authenticator (TOTP)

RFC6238 tabanlı authenticator alternatifi.

Domain

UserOtpChallenge ve VerifyOtp metotları.