14 Nisan 2019 Pazar

ASP.Net Web API TFA Uygulaması



Merhaba,

Google Authenticator tarafından oluşturulan TOTP kodları kullanarak ASP.NET  Web Api si ile istek başına TFA kullanımından kısaca bahsedeceğim.

ASP.NET Web API normalde bir faktöred dayalı kimlik doğrulama ile çalışılır. HTTP kimlik doğrulama üstbilgisi temel düzeninde gönderilen bir şifrenin bilgi faktörüdür. 

Birkaç önemli API için çağrılar, ASP.NET Web API, güvenliği iki faktörlü güvenliğe yükseltir ve TOTP kodunu ek olarak talep eder. 

Transfers Controller sınıfındaki HTTP POST işleyen eylem yöntemi, TFA kullanarak güvenceye almaya çalışacağımız şeydir. Eylem yöntemlerini seçmeli olarak güvenceye almamız gerekiyor.  TFA'yı uygulamak için Yetkilendirme filtresini alt sınıfa seçeriz. Bu filtre, TOTP kodunun istemci uygulaması tarafından özel bir HTTP isteği başlığında adının gönderilmesini bekler.  Tüm API çağrıları için parolanın bilgi faktörü gerekir. Dolayısıyla, ilgili kimlik doğrulama bir mesaj işleyicide mantık uygulanacak.

Aşağıda, istek başına TFA sabitini uygulamak için gereken adımlar vardır.

Transfers Controller sınıfının HTTP POST eylem yöntemi:



public class BasicAuthenticationHandler : DelegatingHandler
{

 private const string SCHEME = "Basic";
 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
 var headers = request.Headers;
  if (headers.Authorization != null && SCHEME.Equals(headers.Authorization.Scheme))
  {
    string credentials = Encoding.UTF8.GetString(Convert.FromBase64String(                                                                                                                  headers.Authorization.Parameter));
    string[] parts = credentials.Split(':');
    string userId = parts[0].Trim();
    string password = parts[1].Trim();
     // TODO - Kullanıcı id ve şifrenin burada saklandığının doğrulanması.

if (true)
{
   var claims = new List<Claim>
{
  new Claim(ClaimTypes.Name, userId)
};


var principal = new ClaimsPrincipal(new[] {new ClaimsIdentity(claims, SCHEME) });

Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
   HttpContext.Current.User = principal;
}
}

 var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
  response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(SCHEME));
}
  return response;
}
}





Mesaj işleyicileri listesine eklemek; 
App_Start klasörü altındaki WebApiConfig.cs,  config.MessageHandlers.Add

(new BasicAuthenticationHandler ());



  • HttpRequestMessage sınıfına bir uzantı yöntemi ekleyeceğiz,
  • Helpers klasöründe RequestHelper adlı yeni bir statik sınıf oluşturacağız.
  • Sınıfı oluşturmadan önce klasörü oluşturacağız,
  • TOTP kodu X-TOTP'de mevcutsa, yöntem onu alır.
  • Sonra statik bir yöntem GetPastCurrentFutureOtp çağırır Son 30 saniyeye karşılık gelen Totp sınıfında bloklanan üç TOTP kodunu döndürülür , şuanki  TOTP ve bir sonraki 30 saniyelik bloğa karşılık gelen ikinci TOTP bloklarına uyanlar alınır.

Örnek aşağıdaki gibi olacaktır.

Bu kod çalıştırıldığında saatin 08:35:07 AM olduğunu kabul ettiğimizde bize aşağıdaki gibi 3 TOTP kodu dönecektir.

  1. TOTP zaman aralığındaki 08:34:30 AM–08:34:59 AM (Geçmiş).
  2. TOTP zaman aralığındaki 08:35:00 AM–08:35:29 AM (Şuanki).
  3. TOTP zaman aralığındaki 08:35:30 AM–08:35:59 AM (Gelecek)  


RequestHelper Sınıfını eklemek;

public static class RequestHelper
{
 public static bool HasValidTotp(this HttpRequestMessage request, string key)
{
 if (request.Headers.Contains("X-TOTP"))
{
   string totp = request.Headers.GetValues("X-TOTP").First();
   // Geçmiş , şimdiki ve gelecekteki TOTP kontrolü
 
  if (Totp.GetPastCurrentFutureOtp(key).Any(p => p.Equals(totp)))
   return true;
}
   return false;
}
}
  


Gelen TOTP gelen 3 şifreden biri ile eşleşiyorsa kodun doğruluğu kabul edilir.  Buradaki 3 OTP den birinin olmasının nedeni ise Sunucudaki saatin, mobil telefondaki saatin, kodun çalıştığı yerdeki saatin Google Authenticator tarafından oluşturulmasıdır.


Infrastructure  klasöründe yeni bir Totp adında sınıf oluşturup, Listede sadece GetPastCurrentFutureOtp  fonksiyonu yer alsın.



public class Totp
{
public static IList<string> GetPastCurrentFutureOtp(string base32EncodedSecret)
{

DateTime epochStart = new DateTime(1970, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc);
long counter = (long)Math.Floor((DateTime.UtcNow - epochStart).TotalSeconds / 30);
var otps = new List<string>();
otps.Add(GetHotp(base32EncodedSecret, counter - 1)); // Bir önceki  OTP
otps.Add(GetHotp(base32EncodedSecret, counter)); // Şuanki OTP
otps.Add(GetHotp(base32EncodedSecret, counter + 1)); // Bir sonraki OTP
return otps;
}
private static string GetHotp(string base32EncodedSecret, long counter)
{
byte[] message = BitConverter.GetBytes(counter).Reverse().ToArray(); 
byte[] secret = base32EncodedSecret.ToByteArray();
HMACSHA1 hmac = new HMACSHA1(secret, true);
byte[] hash = hmac.ComputeHash(message);
int offset = hash[hash.Length - 1] & 0xf;
int truncatedHash = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) |((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
int hotp = truncatedHash % 1000000;
return hotp.ToString().PadLeft(6, '0'); 

}
}



Daha önceden kullanılan delegasyon işleyicisi şifrenin bilgi faktörüne dayanan doğrulanmış kimliğini oluşturur.

Kullanıcı adı alınması için IsAuthorized yöntemini override ederek, messagehandler  Thread.CurrentPrincipal üzerinden kullanalım.

Kullanıcı için örnekte hardcoded şifre girişi yapılmıştı. Bunun yerine otomatik üretiken şifre kullanılması daha uygun olur. 

HasValidTotp uzantısını kullanarak, gelen TOTP'yi doğrulayın ve geri dönün, Gelen TOTP geçerliyse geçerlidir. Aksi takdirde, false döndürün.

Burada false döndürmek, 401 yanıt statüsüne neden olur ( Yetkisiz.)
401 ile birlikte, TOTP’nin gerektirdiğini belirten bir sebep ifadesi gönderin.

HandleUnauthorizedRequest yönteminin geçersiz kılınması sağlamak.

Two Factor Attribute sınıfı Infrastructure klasörü altında yaratalım.



public class TwoFactorAttribute : AuthorizeAttribute
{

protected override bool IsAuthorized(HttpActionContext context)
{

  IIdentity identity = Thread.CurrentPrincipal.Identity;
  if (identity.IsAuthenticated && !String.IsNullOrWhiteSpace(identity.Name))
{

string key = "JBSWY3DPEHPK3PXP";
if (context.Request.HasValidTotp(key))
{
 return true;
}
}
 return false;
}

protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{

actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
ReasonPhrase = "TOTP code required"
}; } }
  



TransfersController adı ile yeni bir  Web API denetleyicisi oluşturup, İşlem sonrasında Two Factor filtresini uygularız. 

İstek sonucunda geçerli bir TOTP içeren X-TOTP başlığı içerdiğinden emin olmamız lazım, Değilse 401 izinsiz durum kodu geri döndürürüz.