Asp.NET Core Identity – Cookie Bazlı Kimlik Doğrulama – IX
Merhaba,
Bu içeriğimizde; artık Asp.NET Core Identity yazı dizimizde sıra Cookie bazlı kimlik doğrulama mekanizmasını inşa etmeye geldiğinden dolayı web uygulamamızda kullanıcı doğrulama kontrolünün Cookie mekanizmasıyla nasıl yapılandırıldığını inceleyecek ve uygulamalı olarak örneklendireceğiz.
Yine uygulamayı yazı dizimizde şu ana kadar bizlere eşlik eden malum projemiz üzerinden pratiğe dökeceğiz. Evet, hadi başlayalım…
Uygulamada Temel Cookie Konfigürasyonunun Yapılandırılması
Uygulamada Cookie bazlı kimlik doğrulaması yapabilmek için herşeyden önce Cookie konfigürasyonunun gerçekleştirilmesi gerekmekte ve bunun için her zaman olduğu gibi “Startup.cs” dosyasında çalışılması gerekmektedir.
public class Startup { public IConfiguration Configuration { get; set; } public Startup(IConfiguration configuration) => Configuration = configuration; public void ConfigureServices(IServiceCollection services) { services.AddDbContext<AppDbContext>(_ => _.UseSqlServer(Configuration["ConnectionStrings:SqlServerConnectionString"])); services.AddIdentity<AppUser, AppRole>(_ => { _.Password.RequiredLength = 5; //En az kaç karakterli olması gerektiğini belirtiyoruz. _.Password.RequireNonAlphanumeric = false; //Alfanumerik zorunluluğunu kaldırıyoruz. _.Password.RequireLowercase = false; //Küçük harf zorunluluğunu kaldırıyoruz. _.Password.RequireUppercase = false; //Büyük harf zorunluluğunu kaldırıyoruz. _.Password.RequireDigit = false; //0-9 arası sayısal karakter zorunluluğunu kaldırıyoruz. _.User.RequireUniqueEmail = true; //Email adreslerini tekilleştiriyoruz. _.User.AllowedUserNameCharacters = "abcçdefghiıjklmnoöpqrsştuüvwxyzABCÇDEFGHIİJKLMNOÖPQRSŞTUÜVWXYZ0123456789-._@+"; //Kullanıcı adında geçerli olan karakterleri belirtiyoruz. }).AddPasswordValidator<CustomPasswordValidation>() .AddUserValidator<CustomUserValidation>() .AddErrorDescriber<CustomIdentityErrorDescriber>().AddEntityFrameworkStores<AppDbContext>(); services.ConfigureApplicationCookie(_ => { _.LoginPath = new PathString("/User/Login"); _.Cookie = new CookieBuilder { Name = "AspNetCoreIdentityExampleCookie", //Oluşturulacak Cookie'yi isimlendiriyoruz. HttpOnly = false, //Kötü niyetli insanların client-side tarafından Cookie'ye erişmesini engelliyoruz. Expiration = TimeSpan.FromMinutes(2), //Oluşturulacak Cookie'nin vadesini belirliyoruz. SameSite = SameSiteMode.Lax, //Top level navigasyonlara sebep olmayan requestlere Cookie'nin gönderilmemesini belirtiyoruz. SecurePolicy = CookieSecurePolicy.Always //HTTPS üzerinden erişilebilir yapıyoruz. }; _.SlidingExpiration = true; //Expiration süresinin yarısı kadar süre zarfında istekte bulunulursa eğer geri kalan yarısını tekrar sıfırlayarak ilk ayarlanan süreyi tazeleyecektir. _.ExpireTimeSpan = TimeSpan.FromMinutes(2); //CookieBuilder nesnesinde tanımlanan Expiration değerinin varsayılan değerlerle ezilme ihtimaline karşın tekrardan Cookie vadesi burada da belirtiliyor. }); services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); app.UseStatusCodePages(); app.UseStaticFiles(); app.UseAuthentication(); app.UseMvc(_ => _.MapRoute("Default", "{controller=Home}/{action=Index}/{id?}")); } }
Yukarıdaki “Startup.cs” dosyasının kodlarını incelerseniz eğer “ConfigureServices” metodu içerisinde uygulamaya 22 ile 35. satırları arasında eklenen “ConfigureApplicationCookie” servisine göz atarsanız eğer parametre olarak belirtilen lambda ifadesiyle belli başlı tanımlamalar yapılmıştır. Bu tanımlamaların ne olduğuna dair açıklamalar kod kısmında yorum satırı olarak yanlarına yazılmıştır.
İşte bu şekilde uygulamada kullanılacak olan Cookie yapılanmasının temel konfigürasyonunu sağlamış bulunmaktayız. Burada devam etmeksizin “CookieBuilder” nesnesinin “SameSite” ve “SecurePolicy” olmak üzere iki propertysi üzerinde durmak ve detaylandırmak istiyorum.
SameSite | SecurePolicy |
SameSite, uygulamamıza ait Cookie bilgilerinin 3. taraflardan kaynaklanan isteklere gönderilip gönderilmemesi ayarını yaptığımız bir özelliktir. “None”, “Strict” ve “Lax” olmak üzere üç farklı değer alır.
|
SecurePolicy, uygulamamıza ait Cookie bilgilerinin güvenilir(HTTPS) ya da güvensiz(HTTP) üzerinden erişilebilir olup olmamasını ayarladığımız özelliktir. “Always”, “SameAsRequest” ve “None” olmak üzere üç farklı değer alır.
|
Login Viewmodel Tasarımı
Şimdi geliştirmeye devam edersek eğer sırada doğrulama yapılacak kullanıcı bilgilerini tarafımıza ulaştıracak viewmodel nesnemizi tasarlayalım. Bunun için “Models” -> “ViewModels” klasörüne “LoginViewModel” adında bir sınıf oluşturalım ve aşağıdaki gibi tasarlayalım.
public class LoginViewModel { [Required(ErrorMessage = "Lütfen e-posta adresini boş geçmeyiniz.")] [DataType(DataType.EmailAddress, ErrorMessage = "Lütfen uygun formatta e-posta adresi giriniz.")] [Display(Name = "E-Posta ")] public string Email { get; set; } [Required(ErrorMessage = "Lütfen şifreyi boş geçmeyiniz.")] [DataType(DataType.Password, ErrorMessage = "Lütfen uygun formatta şifre giriniz.")] [Display(Name = "Şifre")] public string Password { get; set; } /// <summary> /// Beni hatırla... /// </summary> [Display(Name = "Beni Hatırla")] public bool Persistent { get; set; } public bool Lock { get; set; } }
Bu viewmodele göz atarsanız eğer giriş yapacak kullanıcının e-posta adresini ve şifresini barındırmakla birlikte “Persistent” ve “Lock” isminde iki adet bool tipinde propertyde barındırmaktadır. Bu dikkat çekici model elemanlarının sırasıyla ne olduklarına değinirsek eğer; “Persistent”, cookie konfigürasyonlarında Expiration değeri olarak verilen vade süresinin oluşturulacak cookie için geçerli/aktif olup olmamasını tutmakta, “Lock” özelliği ise kullanıcının belirli sayıda yapmış olduğu oturum girişi hatalarında ilgili user profilinin kilitlenip kilitlenmeyeceğini tutmaktadır.
Evet… Artık uygulamamızda Cookie bazlı kimlik doğrulamayı kullanabiliriz.
Login Action Metodunun Tasarlanması ve SignInManager Sınıfının Tanımlanması
Uygulamada giriş sorumluluğunu üstlenecek olan bir “Login” action metodu tasarlamamız gerekmektedir. Bunun için yukarıdaki “Startup.cs” dosyasının 24. satırında belirtildiği gibi önceden(önceki içeriklerde) oluşturduğumuz “User(Controller).cs” controller sınıfını kullanacağız. Tabi siz isterseniz kendinize özel farklı bir controllerda çalışmalarınızı gerçekleştirebilirsiniz.
public class UserController : Controller { readonly UserManager<AppUser> _userManager; readonly SignInManager<AppUser> _signInManager; public UserController(UserManager<AppUser> userManager, SignInManager<AppUser> signInManager) { _userManager = userManager; _signInManager = signInManager; } public IActionResult Login(string ReturnUrl) { TempData["returnUrl"] = ReturnUrl; return View(); } [HttpPost] public async Task<IActionResult> Login(LoginViewModel model) { if (ModelState.IsValid) { AppUser user = await _userManager.FindByEmailAsync(model.Email); if (user != null) { //İlgili kullanıcıya dair önceden oluşturulmuş bir Cookie varsa siliyoruz. await _signInManager.SignOutAsync(); Microsoft.AspNetCore.Identity.SignInResult result = await _signInManager.PasswordSignInAsync(user, model.Password, model.Persistent, model.Lock); if (result.Succeeded) return Redirect(TempData["returnUrl"].ToString()); } else { ModelState.AddModelError("NotUser", "Böyle bir kullanıcı bulunmamaktadır."); ModelState.AddModelError("NotUser2", "E-posta veya şifre yanlış."); } } return View(model); } . . diğer actionlar . . }
Yukarıdaki kod bloğunu incelerseniz eğer “Login” actionının GET metodunda “ReturnUrl” parametresi alınmış ve TempData kontrolüne atanmıştır. Bunun nedeni, Identity mekanizması herhangi bir kullanıcının yetkisinin olmadığı sayfalara erişmeye yahut yetkisi dışında bir iş yapmaya çalıştığında otomatik bir şekilde direkt olarak “Login” actionına yönlendirecektir. İşte bu actionda veritabanıyla tutarlı veriler eşliğinde bir doğrulama gerçekleştirilirse eğer kullanıcıyı ilk gitmek istediği adrese yönlendirmekteyiz.
“Login” actionının POST metoduna göz atarsak eğer 21. satırda kullanıcıdan gelen email adresine uygun olan bir user varsa çekilmekte ve 25. satırda ilgili kullanıcıya ait önceden oluşturulmuş olan cookieler varsa da temizlenmektedir. 26. satırda ise kullanıcıya SignInManager sınıfının PasswordSignInAsync metoduyla oturum açmasına izin vermekteyiz.
Peki nedir bu SignInManager sınıfı?
Kullanıcının giriş ve çıkışlarını kontrol eden bir sınıftır.
Devamında ise kullanıcı tarafından girilen email adresinin yanlış olma durumunda 33 ve 34. satırlarda ModelState’e error olarak ilgili hata mesajları eklenmekte ve böylece kullanıcıya bilgi verilmektedir.
Ayriyetten; 26. satıra tekrardan göz atarsanız eğer SignInManager sınıfının PasswordSignInAsync metodunun 3. parametresine true verildiği taktirde oluşturulacak cookie değerinin Expiration olarak belirtilen vade kadar tutulacağını ifade etmekte aksi taktirde session açık kaldığı sürece coockie değerlerinin kullanılabileceğini lakin browser kapatıldığı vakit cookielerin temizleneceğini ifade etmektedir.. 4. parametrede ise başarısız neticelenen n adet giriş denemelerinde ilgili kullanıcının hesabının kilitlenip kilitlenmeme durumunu kontrol etmiş oluyoruz. Bu konuyu makalemizin ileriki satırlarında ayrıca ele alacak ve detaylandıracağız.
Velhasıl oluşturduğumuz “Login” actionının view görüntüsünü aşağıdaki gibi tasarlayalım.
@model AspNetCoreIdentityExample.Models.ViewModels.LoginViewModel @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers <h4>Giriş Yap</h4> <hr /> <form asp-action="Login"> <table> <tr> <td colspan="3"><div asp-validation-summary="All"></div></td> </tr> <tr> <td><label asp-for="Email"></label></td> <td><input asp-for="Email" /></td> <td><span asp-validation-for="Email"></span></td> </tr> <tr> <td><label asp-for="Password"></label></td> <td><input asp-for="Password" /></td> <td><span asp-validation-for="Password"></span></td> </tr> <tr> <td><label asp-for="Persistent"></label></td> <td><input asp-for="Persistent" /></td> <td><span asp-validation-for="Persistent"></span></td> </tr> <tr> <td colspan="3"><input type="submit" value="Giriş Yap" /></td> </tr> </table> </form>
Authorize Attribute’u İle Yetki Kontrolü
Artık uygulamamızda cookie bazlı kimlik doğrulaması tam olarak inşa edilmiş bulunmaktadır. Dolayısıyla bu safhadan itibaren sayfa bazlı yetki kontrolü gerçekleştirmemiz yeterli ve yerinde olacaktır. Bunun için Authorize attributeunun kullanılması yeterlidir. Burada örneklendirme için “User(Controller).cs” controllerı altında “Index” actionını seçiyorum ve aşağıda olduğu gibi Authorize attributeu ile işaretliyorum.
public class UserController : Controller { readonly UserManager<AppUser> _userManager; readonly SignInManager<AppUser> _signInManager; public UserController(UserManager<AppUser> userManager, SignInManager<AppUser> signInManager) { _userManager = userManager; _signInManager = signInManager; } [Authorize] public IActionResult Index() { return View(_userManager.Users); } . . . diğer actionlar . . . }
Bu işlemden sonra ilgili sayfaya giriş yapmaksızın girmeye çalıştığımızda aşağıdaki ekran görüntüsünde görüldüğü üzere “user/login” dizinine yönlendirme yapacaktır.
Peki giriş(login) yaptığımızda ne oluyor? diye sorarsanız eğer onunda cevabını aşağıdaki ekran görüntüsü vermektedir.
Yukarıdaki ekran görüntüsünü incelerseniz eğer tarayıcıya sağ tıklayarak “İncele” dedikten sonra “Application” sekmesini açtığınızda sol tarafta çıkan menü üzerinden “Cookies” sekmesinde uygulamamıza ait cookieleri görebilmekteyiz. Dikkat ederseniz uygulama login yaptığı taktirde Startup.cs dosyasında modifiye ettiğimiz CookieBuilder nesnesindeki “AspNetCoreIdentityExampleCookie” name değerini verdiğimiz bir cookie oluşturulmuştur. Bunun dışında login işleminden önce Identity Asp.NET Core uygulaması tarafından eklenen varsayılan cookieler mevcuttur. Bunlar Asp.NET MVC’de CSRF/XSRF(Cross-Site Request Forgery) Saldırı Güvenliği ile alakalı önlemler üzerine yapılmış mimarisel çalışmalardır.
Eğer ki “Application” penceresindeki bu cookieleri manuel silersek uygulama logout yapmış olacaktır. Bunun için ise “User(Controller).cs” controllerında ayriyetten bir “Logout” isminde action metot tasarlayarak bu işlemi daha da programatize edebiliriz.
public class UserController : Controller { readonly UserManager<AppUser> _userManager; readonly SignInManager<AppUser> _signInManager; public UserController(UserManager<AppUser> userManager, SignInManager<AppUser> signInManager) { _userManager = userManager; _signInManager = signInManager; } public async Task<IActionResult> Logout() { await _signInManager.SignOutAsync(); return RedirectToAction("Index"); } . . . diğer actionlar . . . }
Sonuç olarak Asp.NET Core Identity mekanizmasında Cookie bazlı kimlik doğrulamasını tüm detaylarıyla gerçekleştirmiş olduk. Bir sonraki içeriğimizde belli bir sayıda başarısız oturum açma durumlarında hesabına girmeye çalışan ilgili kullanıcının hesabının sonraki girişleri doğru bilgilerle dahi olsa nasıl kilitleneceğini ele alacağız.
O halde şimdilik görüşmek üzere 🙂
İlgilenenlerin faydalanması dileğiyle…
İyi çalışmalar…
Not : Örnek projeyi indirmek için buraya tıklayınız.
Hocam merhaba;
await _signInManager.SignOutAsync(); Hata veriyor.
Hata mesajı : NullReferenceException: Object reference not set to an instance of an object.s
Startup.cs içeriğini komple paylaşır mısın?
Giriş yapılıyor ve Cookie geliyor ama [Authorize] Olan yerlere yine girmiyor.
Eğer ki Asp.NET Core 3.0+ sürümlerinde çalışıyorsanız “Startup.cs” dosyasındaki “Configure” metodu içerisinde “UseAuthentication” middlewareinin yanında “UseAuthorization” olduğuna dikkat ediniz.
Merhabalar,
Login postumda var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.Persistent, model.Lock);
kısmı derlenirken
Cannot create a DbSet for ‘IdentityRole’ because this type is not included in the model for the context. diye hata alıyorum
merhaba,startup içerisinde UseAuthorization komutunu yazdıgımda altını çiziyor.
bu konuda ne yapabilirim.
Merhaba,
İlgili kodu görsel olarak paylaşır mısınız?
Hata burada sanırım bu startup cs doğru mu acaba
app.UseAuthentication();
app.UseAuthorization();
olması daha doğru olmaz mı ? 103 ve 102. satırlar
Hocam ben kedi sitemde bu yaptıklrınızı yaptım. Localde çalışıyor ama gereçek ortamda cookie nin daha çok süresi varken 10 ya da 15 dk sonra falan cookie siliniyor bu duruma ne sebep olmuş olabilir? Benim için çok önemli lütfen yardımcı olabilir misiniz?
Sunucuya danışmanı tavsiye ederim.
Natro ile yayınladım sitemi session süreleri maximum 30 dk imiş bununla alakalı olabilir mi?
Bir de new CookieBilder içindeki Expiration u vermeye çalıştığımda hata veriyor bundan dolayı olabilir mi?
Bir de sitenizi ve içeriklerinizi beğeniyorum ama yapılan yorumlara cevap geldiğinde mail ile bilgilendirme olursa daha güzel olur 🙂 sitenizi geliştirebilmeniz için bir tavsiye.
Cevabınızı bekliyorum
Teşekkür ederim:)
Evet olabilir.
CookieBuilder’de ki Expiration’a değer atamamanızı öneririm.
Evet, bu öneri üzerine ilk fırsatta yorumlar notification işlemi için bir eklenti vs. araştırmaya başlayacağım…
Sevgiler…
Hocam, benim admin, bayi, üye rolünde 3 farklı kullanıcım var. Login işlemini _usermanager.signin() methoduyla yapıyorum. Bu kullanıcılar aynı tarayıca oturum açamıyorum. Farklı kullanıcı giriş yaptğında diğer oturum kapanıyor. Bunu önlemenin bir yolu var mı ? Yani aynı tarayıca 2 farklı kullanıcıda oturum açmam mümkün mü cevaplandırırsanız sevinirim kolay gelsin
‘Authentication Schema’ları incelemeniz tavsiye ederim. Yapı yine aynı. Sadece farklı şemalarda farklı türlere göre authentication konfigürasyonlarını sağlaman gerekmektedir.
Merabalar Hocam.Projeyi calıştırdığımda :
-OptionsValidationException: Cookie.Expiration is ignored, use ExpireTimeSpan instead
hatayı alıyorum.Sorunu çözmemde yardımcı olur musunuz ?
Çözebildinizmi ?
bu hatayı çözebildiniz mi
Bu ifade yerine
bunu deneyin
Bu arada MaxAge = TimeSpan.FromMinutes(120) 120 ifadesi dakika cinsindendir bunu kendinize göre ayarlayabilirsiniz.
Autentifikasyon icin Cookie konfiqurasyonu zamani:
Hocam merhabalar.
Bu Action metoduına ReturnUrl bilgisinin gelme ayarını nerde yaptınız acaba makaleyi ikinci okuyuşum hala anlayamadım. Startup.cs içerisinde veya başka bir yerde böyle bir ayara denk gelmedim acaba gözden mi kaçırıyorum, yoksa bilmediğim birşey mi var.
Merhaba,
Login’e yönlendirildiğin taktirde hangi sayfaya girmek istediyseniz o otomatik olarak ReturnUrl parametresiyle gelecektir.
İyi çalışmalar.
Merhaba Hocam client-server projem var, serverde jwt token kullaniyorum,client tarafda elde etdiyim access,refresh tokenleri ise cookiede tutmak isdiyorum , bunu nasil yapmaliyim.biraz zorunluk cekiyorum.tesekkurler.
Merhaba,
Login ekranında her zaman değil ancak bazen formu post ettiğimde http 400 bad request hatası alıyorum. Her zaman olmaması hatayı bulamamama sebep oluyor. Sebebi ne olabilir yardımcı olabilir misiniz hocam hayırlı günler.