Asp.Net Core Identity: Temel Bilgiler ve Güvenliğe Yönelik İpuçları

Asp.Net Core Identity: Basics and Security Tips

Cihat Solak
Intertech

--

Identity Microsoft tarafından geliştirilen ve üyelik sistemi inşa etmek amacıyla kullanılan bir kütüphanedir. Aynı zamanda bir API olarak da hizmet verir. Bu bağlamda, Library ve API terimleri eş anlamlı olarak kullanılmaktadır.

Uygulamalar genellikle üyelik sistemine ihtiyaç duyar. Bu ihtiyaç doğduğunda, sıfırdan bir sistem kodlamak yerine, hazır kütüphanelerden biri olan Identity’i kullanmak, hız açısından önemli avantajlar sağlar. Identity’nin kendi temel tabloları bulunmaktadır ve bu tablolar, üyelik sistemi için temel yapı taşlarını oluşturur. Bu tablolarda gereksiz karmaşıklık bulunmamaktadır ve ihtiyaç duyulduğunda tablolara müdahale edilebilir. Ayrıca, ilgili tabloların OAuth 2.0 ve OpenID Connect gibi protokollere uygun olması, bu tabloları kullanarak farklı tablolar oluşturmak için zaman ve çaba harcamaktan kaçınmamıza olanak tanır.

Identity’nin sunduğu tabloların yanı sıra, isteğe bağlı olarak özel tablolar da oluşturulabilir. Özellikle kullanıcı ve rol tablolarına doğrudan müdahale edebilme yeteneği önemli bir avantaj sağlar.

Nasıl Kullanılır?

Identity mimarisi, EF Core CodeFirst yaklaşımıyla hızlı bir şekilde veri tabanında oluşturulabilir. Identity içerisinde herhangi bir kısıtlama bulunmamaktadır. SOLID prensiplerine dayanarak, geliştirmeye açık ancak değişime kapalı prensibiyle genişletilebilmektedir.

Üyelik Sistemi Üzerinde Temel Bilgiler

🍪 Cookie (Çerez): Kullanıcının bilgisayarında saklanan ve kullanıcıyla ilgili bilgiler içeren verilerdir. Tarayıcı her istek yaptığında, ilgili çerez otomatik olarak requeste eklenir. Bu sayede, sunucuya yapılan her istekle birlikte, hangi kullanıcı tarafından yapıldığını anlamamıza olanak tanır.

🔐 Authentication (Kimlik Doğrulama): Kullanıcının, kullanıcı adı-parola veya e-posta-parola gibi bilgilerle sisteme giriş yapmasını ifade eder. Bu adım, sisteme giriş yapmak isteyen kullanıcının kimlik bilgilerinin geçerli olup olmadığını kontrol etme işlemidir. Örneğin, bir web sayfasının belirli bir bölümüne üye olan kullanıcılar giriş yapabilir. Bu kullanıcıların giriş yapabilmesi için önce login olarak adlandırılan bir kimlik doğrulama adımı gerçekleştirilmelidir.

Authorization (Kimlik yetkilendirme): Kimliği doğrulanmış bir kullanıcının sahip olduğu yetkileri belirleme işlemidir. Bazı sayfalara sadece admin rolündeki kullanıcıların erişimine izin vermek istiyor olabiliriz. Bu durumda devreye kimlik yetkilendirme girer. Kimlik yetkilendirme sürecinde, kullanıcı tekrar giriş yapma sayfasına yönlendirilmez. Örneğin, kimlik doğrulama aşamasında, kullanıcı sadece üyelerin erişmek istediği bir sayfaya gitmek isterse ve daha önce giriş yapmadıysa, giriş sayfasına yönlendirilir. Ancak kimlik yetkilendirme aşamasında, kullanıcı zaten daha önce giriş yapmıştır, bu nedenle hata sayfasına yönlendirilir ve Erişiminiz yok gibi bir hata mesajı alır. Bu süreçte giriş sayfasına yönlendirme işlemi olmaz, sadece hata sayfası gösterimi gerçekleşir.

Claims (key-value): Özellikle OAuth 2.0 ve OpenID Connect protokollerinde önemli bir yapıdır. Kullanıcıyla ilgili bilgileri tutan key-value çiftleridir. Cookie’lerden farkı, kullanıcı bilgilerini depolama şeklidir. Örneğin, e-posta, yaş, doğum tarihi gibi bilgileri içerir. Cookie tabanlı üyelik sistemlerinde kullanılır. Cookie’lerden okunan key-value çiftleri birer claim nesnesini temsil eder. Claim, key ve value özelliklerine sahip bir sınıftır. Bu bilgiler sadece cookie’lerde değil, token’ın payload’ında da bulunabilir. Kısacası, claim’ler, kullanıcı bilgilerini temsil eder ve sadece cookie’lerle sınırlı değildir.

Role: Kimlik yetkilendirme sürecinde kullanabileceğimiz türdür.

Third-Party Authentication: Kullanıcıların üyelik sistemimizi kullanmak yerine, tercih ettikleri platformlar olan Facebook, Google veya Microsoft gibi hesaplarla giriş yapmalarını içerir. Bu senaryolarda, kullanıcı bilgilerini uygulamamız, ilgili platformlardan alacaktır. Eğer üyelik sistemimizde, örneğin Facebook veya Google ile giriş yap gibi seçenekler sunarak, kişinin üyelik işlemini üçüncü taraf bilgilerle gerçekleştirmesini istiyorsak, bu durum Third-Party Authentication yani üçüncü taraf kimlik doğrulama olarak adlandırılır.

🍪 Cookie-Based Authentication

API’lerde kimlik yetkilendirme ve doğrulama işlemleri genellikle token üzerinden gerçekleşir. Single Page Application’lar (Angular, React, Vue.js gibi) genellikle kimlik doğrulama ve yetkilendirme işlemlerini de token üzerinden yaparlar. Ancak, normal bir web sayfası için (server-side web sayfaları), yani SPA’lar olmadan, ASP.NET Core MVC veya Node.js gibi server-side uygulamalar geliştirecekseniz, yetkilendirmeyi cookie üzerinden gerçekleştirmek en uygun yaklaşım olacaktır.

Token kullanımı, özellikle bir API uygulaması geliştiriyorsanız ve sadece dış dünyaya endpointlerle veri sağladığınız bir üyelik sistemi oluşturmak istiyorsanız, büyük bir öneme sahiptir. Örneğin, bir SPA geliştiriyorsanız (React, Vue, Angular gibi), SPA’lar kendi başlarına veri barındırmazlar ve mutlaka bir backend ile iletişim kurmak zorundadırlar. Bu durumda, Single Page Application’ın backend ile iletişiminde kimlik doğrulama ve yetkilendirme işlemleri genellikle token üzerinden gerçekleşir.

Cookie tabanlı kimlik doğrulama, token tabanlı kimlik doğrulamaya göre daha kolay yönetilebilir. Çünkü tarayıcı her istekle birlikte cookie’yi otomatik olarak sunucuya gönderir. Token tabanlı kimlik doğrulama kullanılsaydı ve bu token local storage’da saklansaydı, her istekle birlikte bu token’ı manuel olarak göndermek gerekecekti. Yani, SPA’ların token’ları her istekle birlikte kodlaması gerekirdi. Ancak, cookie tabanlı kimlik doğrulama kullanılıyorsa, tarayıcı zaten her istekle birlikte cookie’yi otomatik olarak ekler.

Claim-Based Authorization

Dinamik yetkilendirme, sıklıkla “Claim Bazlı Yetkilendirme” olarak da adlandırılır. “Claim” terimi, .NET Core’a özgü olmayan ve birçok programlama dilinde karşılaşılan bir kavramdır. Bu terimin kökeni OAuth 2.0 protokolünden gelir.

OAuth 2.0, bir protokoldür ve client ile server arasındaki kimlik yetkilendirme sürecinin kurallarını belirler. Bir protokolün varlığı, belirli bir sürecin uygulanması için gereken kuralların bir listesini içerdiği anlamına gelir. OAuth 2.0 protokolü, endüstri standardında kabul görmüş bir protokoldür ve bu protokolün dokümantasyonunda claim terimi geçer.

Claim yapısı, kullanıcı verilerinin tutulduğu ve yetkilendirme işlemleri için kullanılan önemli bir yapıdır. Örneğin kullanıcının doğum tarihi claim yapısı içerisinde tutulabilir. Daha sonra borsa uygulamasında 18 yaşından küçük olan kullanıcıların borsa bilgilerini görememesi sağlanabilir. Bu, dinamik yetkilendirme örneğidir.

Eğer kullanıcıyla ilgili veriler sadece yetkilendirme amacıyla tutulacaksa, bu verileri AspNetUserClaims tablosunda saklamak uygun olacaktır. Eğer yetkilendirme amacı içermiyorsa, ilgili veriler AspNetUsers tablosunda tutulabilir.

Claims Nerden Geliyor?

Claim’ler, kullanıcının giriş yaptığında oluşturulan cookie içinden gelir. Bu cookie, SignInManager aracılığıyla çağrılan metotlar (sign metotları) tarafından otomatik olarak oluşturulur ve içine kullanıcıyla ilgili rolleri key-value çifti olarak ekler. Bu işlem varsayılan olarak şu şekilde gerçekleşir: Roller otomatik olarak eklenir ve kullanıcıya ait tüm claim bilgileri eklenir.

Eğer istenirse, SignInManager üzerinden gerçekleştirilen signIn işlemi sırasında kendi özel ekleyeceğiniz claim nesnelerini de ekleyebilirsiniz. Bu işlem sonucunda, rol ve claim tablosundaki bilgiler otomatik olarak cookie içine eklenir.

Oluşturulan bu cookie, IDataProtector interface’i üzerinden simetrik olarak şifrelenir. Bu da demektir ki, kimse tarafından decrypt edilemeyeceği anlamına gelir.

🌌 Claim Dönüşümleri (ClaimTransformation) Nasıl Yapılır?

Cookie içindeki değerlerin, framework tarafından claims’lere maplenmesi istendiğinde (her request’te gerçekleşir), araya girilerek claim’lere değer eklemek için kullanılır.

Normalde framework, AspNetUserClaims tablosundaki bilgileri cookie’ye ekler. Ancak varsayalım ki city alanı sadece AspNetUsers tablosunda tutuluyor ve AspNetUserClaims tablosuna eklenmek istenmiyor. Bu durumda IClaimTransformation aracılığıyla claims’ler city alanına eklenebilir.

Bu yöntem, sadece Authorize özniteliğine (attribute) sahip olan yerlerde çalışır.

Her istekte devreye giren bir sınıf olduğu için her istekte çalışır.

Performanslı olması için claim’leri cookie’ye eklemek daha iyidir. Ancak bazen, cookie’ye eklemek yerine IClaimTransformation aracılığıyla runtime sırasında claim’lere eklemek istenebilir.

Eğer AspNetUsers tablosundaki bir değeri AspNetUserClaims tablosuna eklemek istemiyorsanız ve IClaimTransformation yöntemi performans sorunu yaratıyorsa (her istekte çalışarak claims’lere ekleme yapar), o zaman _signInManager.PasswordSignInAsync() metodunu kullanmak yerine _signInManager.SignInWithClaimsAsync() metodunu kullanarak ek claim bilgileriyle beraber cookie oluşturabilir ve kullanıcı giriş yapabilirsiniz.

public class UserClaimProvider : IClaimsTransformation
{
private readonly UserManager<AppUser> _userManager;

public UserClaimProvider(UserManager<AppUser> userManager)
{
_userManager = userManager;
}

public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = principal.Identity as ClaimsIdentity;

var user = await _userManager.FindByNameAsync(claimsIdentity.Name);

if (string.IsNullOrEmpty(user.City))
{
return principal;
}

if (principal.HasClaim(claim => claim.Type != "city"))
{
Claim cityClaim = new("city", user.City);
claimsIdentity.AddClaim(cityClaim);
}

return principal;
}
}

IClaimTransformation, Authorize özniteliği çalışmadan araya girerek claims’lere ekleme yapar. Aslında bu, token veya cookie’de tutmak istenmeyen verileri IClaimTransformation aracılığıyla araya girerek claims’lere eklemenin güzel bir yöntemidir.

👮 Claim Bazlı Yetkilendirme Nasıl Yapılır?

Örneğin, kullanıcının doğum tarihini claim’e ekleyebilirsiniz. Ardından [Authorize(Policy = “ViolenceRestriction”)] attribute’unu action’a ekleyerek 18 yaşından küçük kullanıcıların sayfaya girişini engelleyebilirsiniz.

public class ViolenceRequirement : IAuthorizationRequirement
{
public ViolenceRequirement(int thresholdAge)
{
ThresholdAge = thresholdAge;
}

public int ThresholdAge { get; set; }
}

public class ViolenceRequirementHandler : AuthorizationHandler<ViolenceRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ViolenceRequirement requirement)
{
if (!context.User.HasClaim(claim => claim.Type == "birthdate"))
{
context.Fail();
return Task.CompletedTask;
}

Claim birthDateClaim = context.User.FindFirst("birthdate")!;

var today = DateTime.Now;
var birthDate = Convert.ToDateTime(birthDateClaim.Value);
var age = today.Year - birthDate.Year;

if (birthDate > today.AddYears(-age)) age--;

if (requirement.ThresholdAge > age)
{
context.Fail();
return Task.CompletedTask;
}

context.Succeed(requirement);

return Task.CompletedTask;
}
}

Program.cs içerisindeki WebApplicationBuilder konfigürasyonu yapılmalıdır.

builder.Services.AddScoped<IAuthorizationHandler, ViolenceRequirementHandler>();

builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ViolenceRestriction", policy =>
{
policy.AddRequirements(new ViolenceRequirement(18));
});
});

Minimumda Action metodu [Authorize] attribute’una sahip olmalıdır.

public class PolicyController : Controller
{
[Authorize(Policy = "SpecialPage")]
public IActionResult AgeRestrictedPage()
{
return View();
}
}

Microsoft Identity Tabloları

.NET Core Identity, kimlik doğrulama ve yetkilendirme işlevselliği sağlayan bir kütüphanedir. Bu kütüphanenin varsayılan olarak kullanılan tabloları şunlardır:

AspNetUsers: Bu tablo, uygulamada kayıtlı kullanıcıların bilgilerini tutar. Kullanıcıların kimlik doğrulama bilgileri, adı, e-posta adresi ve telefon numarası gibi bilgileri bu tabloda saklanır.

AspNetRoles: Bu tablo, uygulamada tanımlanan rolleri tutar. Rollere örnek olarak “Admin”, “Moderatör” veya “Kullanıcı” gibi isimler verilebilir.

AspNetUserRoles: Bu tablo, kullanıcıların rollerini tutar. Bu tablo, AspNetUsers tablosu ve AspNetRoles tablosu arasında bir bağlantı oluşturur.

AspNetUserLogins: Bu tablo, sosyal medya hesapları veya diğer harici kimlik doğrulama sağlayıcıları ile kimlik doğrulama yapan kullanıcıların bilgilerini tutar.

AspNetUserClaims: Bu tablo, kullanıcılara atanmış olan kimlik doğrulama taleplerini (claims) tutar. Örneğin, bir kullanıcının rolü, bir kullanıcının doğum tarihi, bir kullanıcının telefon numarası veya e-posta adresi gibi talepler bu tabloda saklanabilir.

AspNetUserTokens: Bu tablo, uygulama içinde kullanılabilecek tokenleri (anahtarları) tutar. Tokenler, kullanıcıların hesaplarına özel olarak oluşturulur ve belirli bir süre için geçerlidir.

Bu tabloların varsayılan olarak sağladığı işlevsellik, uygulamaların kimlik doğrulama ve yetkilendirme işlemlerini kolaylaştırır ve hızlandırır. Ancak, ihtiyaçlarınıza göre bu tabloların yapılarını ve işlevselliğini özelleştirebilirsiniz.

Kullanıcı kritik bilgilerini güncelledikten sonra security stamp 🔒 değerini güncellenmelidir.

await _userManager.UpdateSecurityStampAsync(user);

Role-based Authorization: Rol değerlerinin kontrolüne dayanır.

Claims-based Authorization: Sadece claim’lerin varlığına göre kontrol sağlanır.

Policy-based Authorization: Claim’lerin varlığının yanı sıra iş mantığı (business rules) içeriyorsa kullanılır. Burada “policy” terimi, claim datalarına iş mantığı eklediğimizde ortaya çıkan yetkilendirme kuralını ifade eder. Örneğin, bir claim üzerine 30 gün erişim kuralı eklemek veya doğum tarihine göre erişimi kontrol etmek gibi iş kuralları içerir.

Bu bağlamda, Claim-based Authorization sadece claim’lerin varlığını kontrol ederken, Policy-based Authorization claim’lerin yanı sıra iş mantığı eklenmiş durumları içerir.

--

--