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.

  • None
    Uygulamaya ait Cookie bilgilerini 3. taraf isteğe ekler ve gönderir.
  • Strict
    Uygulamaya iat Cookie bilgilerini 3. taraf hiçbir isteğe göndermez.
  • Lax
    Uygulamaya ait Cookie bilgilerini üst düzey(top level) navigasyonlara sebep olmayan yani bir başka deyişle adres çubuğundaki değişikliklere neden olmayan isteklere göndermeyecektir.
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.

  • Always
    Cookie’leri HTTPS üzerinden erişilebilir yapar.
  • SameAsRequest
    Cookie’leri hem HTTP hemde HTTPS protokolü üzerinden erişilebilir yapar.
  • None
    Cookie’leri HTTP üzerinden erişilebilir yapar.

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.
Asp.NET Core Identity - Cookie Bazlı Kimlik Doğrulama - IX

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.
Asp.NET Core Identity - Cookie Bazlı Kimlik Doğrulama - IX

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
        .
        .
        .
    }

Asp.NET Core Identity - Cookie Bazlı Kimlik Doğrulama - IX

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.

Bunlar da hoşunuza gidebilir...

3 Cevaplar

  1. ishak dedi ki:

    Hocam merhaba;

     [HttpPost]
            [ValidateAntiForgeryToken]
            public async Task SignIn(string Email_Login, string Password, bool Persistent, bool Lock)
            {
    
    
    
                if (ModelState.IsValid)
                {
                    CustomIdentityUser user = await _userManager.FindByEmailAsync(Email_Login);
                    if (user != null)
                    {
                        //İlgili kullanıcıya dair önceden oluşturulmuş bir Cookie varsa siliyoruz.
                        await _signInManager.SignOutAsync();
    

    await _signInManager.SignOutAsync(); Hata veriyor.

    Hata mesajı : NullReferenceException: Object reference not set to an instance of an object.s

  1. 20 Ağustos 2019

    […] Asp.NET Core Identity – Cookie Bazlı Kimlik Doğrulama – IX […]

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*