Asp.Net Core Rol Bazlı Kimlik Doğrulama
Bu yazımda Asp.Net core 3.1 ile rol bazlı yetkilendirmenin identity olmadan nasıl yapılacağını anlatacağım.
Neden Identity Olmadan Yapıyoruz?
İhtiyacımız olan sadece role bazlı doğrulama olduğundan identity’i kullanmak gereksiz olabilir, kapsamı çok daha geniş. Ve bence en önemli neden identity ile açınca direkt yapıp geçiyoruz neyin nasıl çalıştığı hakkında pek fikrimiz olmuyor bir de burada bir sorun çıkarsa çözmesi zaman alabiliyor.
Asp.net Core 3.1 projesi açarak başlayalım.
code-first ile veritabanımızı oluşturacağız. User, Role ve UserRole adlı üç tane tablomuz olacak.
1 2 3 4 5 6 7 8 |
public class User { public int Id { get; set; } public string Name { get; set; } public string FullName { get; set; } public string Password { get; set; } public ICollection<UserRole> UserRoles { get; set; } } |
1 2 3 4 5 6 |
public class Role { public int Id { get; set; } public string Name { get; set; } public ICollection<UserRole> UserRoles { get; set; } } |
1 2 3 4 5 6 7 |
public class UserRole { public int UserId { get; set; } public int RoleId { get; set; } public virtual User User { get; set; } public virtual Role Role { get; set; } } |
Çoka çok ilişkinin veritabanına yansıması için ilgili context sınıfımızda fluent api kullanmalıyız.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class ApplicationContext : DbContext { public ApplicationContext(DbContextOptions options) : base(options){} public DbSet<User> Users { get; set; } public DbSet<Role> Roles { get; set; } public DbSet<UserRole> UserRoles { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<UserRole>() .HasKey(ur => new { ur.UserId, ur.RoleId }); modelBuilder.Entity<UserRole>() .HasOne(ur => ur.User) .WithMany(u => u.UserRoles) .HasForeignKey(ur => ur.UserId); modelBuilder.Entity<UserRole>() .HasOne(ur => ur.Role) .WithMany(u => u.UserRoles) .HasForeignKey(ur => ur.RoleId); } } |
Contextimizi middleware’de tanımlayalım
1 |
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); |
Tablolarımız kullanıma hazır komut satırına aşağıdaki komutları yazıp veritabanına ekleyelim
1 2 |
add-migration Initial update-database |
Startup.cs de cookie ayarlarını tanımlayalım
1 2 3 4 5 |
services.Configure<CookiePolicyOptions>(options => { options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(); |
1 2 3 |
app.UseCookiePolicy(); app.UseAuthentication(); app.UseAuthorization(); |
HomeController’a basit bir login action’ı ekleyelim ve viewini oluşturalım
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<div class="container"> <div class="row"> <div class="col-md-3"> <h3>Login</h3> <form asp-action="Login" method="post"> <div class="form-group"> <label>Name:</label> <input type="text" class="form-control" id="name" name="name" /> </div> <div class="form-group"> <label>Password:</label> <input type="password" class="form-control" id="password" name="password" /> </div> <div class="form-group"> <label></label> <input type="submit" class="btn btn-primary" value="Login" /> </div> </form> </div> </div> </div> |
Şimdi login action’ını yazmadan önce claim kavramını açıklayalım.
Claim: Claim kelimesinin türkçe karşılığı talep etmek/iddia etmek/istek gibi kelimeler denilebilir. Programlama da ise anahtar-değer çiftlerini tutan/sağlayan tiplere denir. Örneklersek; diyelim ki sürücü ehliyeti veren xyz adlı bir kurum var A kişisi de bu kurumdan F tipi 2021’de geçerliliği sona erecek bir ehliyet alsın. Bu durumda iki tane claim oluşacak ilk claim’in ismi veriliş tarihi, değeri 2021 diğer claim’in de ismi ehliyet tipi, değeri de F olacaktır. Bu claimlerin sağlayıcısı xyz adlı kurum, claimlerin ait olduğu kişi de A kişisidir.
ApplicationContext’i homecontroller’a enjekte edelim.
1 2 3 4 5 6 7 |
private readonly ILogger<HomeController> _logger; private ApplicationContext Db; public HomeController(ILogger<HomeController> logger, ApplicationContext applicationContext) { _logger = logger; Db = applicationContext; } |
Login Action’ı:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public IActionResult Login() { return View(); } [HttpPost] public IActionResult Login(string name, string password) { User user = Db.Users.FirstOrDefault(x => x.Name == name); if (user == null) { return View(); } var result = user.Password == password; ClaimsIdentity identity = null; bool isAuthenticate = false; if (result) { var userRoles = Db.UserRoles.Where(ur => ur.UserId == user.Id); var roles = Db.Roles.Where(ro => userRoles.Any(ur => ur.RoleId == ro.Id)); identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); foreach (var item in roles) { identity.AddClaim(new Claim(ClaimTypes.Role, item.Name)); } identity.AddClaim(new Claim(ClaimTypes.Name, name)); isAuthenticate = true; } if (isAuthenticate) { var principal = new ClaimsPrincipal(identity); HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal); return RedirectToAction("Index", "Home"); } return View(); } |
name’i unique olarak düşünün. Eğer name ve password eşleşirse kullanıcının veritabanından rollerini çekip claime kaydedeceğiz. Claimi de cookie de tutacağız.
ClaimIdentity ise dağınık claimlerimizi bir arada tutmasının yanında AuthenticationType’ı da tanımlamamıza yardımıcı oldu.
En son aşamada kullanıcıyı authenticate etmemiz gerekiyor. Bunun içinde ClaimsPrincipal sınıfına ihtiyaç var ClaimsPrincipal da ClaimsIdentity’iyi içine alan bir sınıf. Hiyeraşik olarak şöyle bir dizilim ortaya çıkıyor Claims > ClaimsIdentity > ClaimsPrincipal.
Logout metotu da şöyle olacak:
1 2 3 4 5 |
public IActionResult Logout() { HttpContext.SignOutAsync(); return RedirectToAction("Index", "Home"); } |
Veritabanımıza birkaç rol ve user kaydedip UserRoles tablosunda da tanımlayalım.
Authorize attribute ile de role göre kimlik doğrulamayı sağlayalım.
1 2 3 4 5 6 7 8 9 10 |
[Authorize(Roles = "Member")] public IActionResult Index() { return View(); } [Authorize(Roles = "Admin,Editor")] public IActionResult Privacy() { return View(); } |
Index sayfasına istek gittiğinde önce kullanıcının giriş yapıp yapmadığına bakacak eğer giriş yapmış ise ClaimType.Role keyi ile tutuğumuz değerler ile attribute içindeki eşleşiyor mu kontrol edecek eğer uyuşuyorsa sayfayı açacak.
Özetlersek
Kullanıcının şifre ve kullanıcı adı eşleşiyor ise rolünü ve kullanıcı adını claim olarak cookie’de kaydediyoruz ve bu kaydettiğimiz değerleri rol gerekli actionlarda kontrol edip sonuca göre sayfayı gösteriyoruz.
Bu yazının sonuna geldik okuduğunuz için teşekkürler bir sonraki yazıda görüşmek üzere 🙂
Merhaba,
Öncelikle çok güzel bir makale olmuş çok faydalandım teşekkür ederim.
Merak ettiğim bir konu var, userId erişmini nasıl sağlayacağız? Claimlere userId eklemedik fakat işlemlerimde userId gerekecek yanıtlayabilirseniz çok sevineceğim şimdiden teşekkürler,
iyi çalışmalar,
Merhaba teşekkür ederim, login olduktan sonra claimleri eklediğimiz yerde identity.AddClaim(new Claim(“key ismi”, user.Id)); gibi bir key ile ekleyebilirsin. Getirirken de HttpContext.User.FindFirstValue(“key ismi”); kullanabilirsin.
İyi çalışmalar