Asp.NET Core Identity – Şifremi Unuttum – XI

Merhaba,

Asp.NET Core Identity yazı dizimizin 11. makalesinde web sitemize üye olan kullanıcılar tarafından belirtilen şifrelerin -insanlık hali- unutulması durumunda kullanıcının nasıl yeni şifre talep edeceğini Identity mekanizması üzerinden inceleyeceğiz.

Süreçte bütünlüğü sağlayabilmek adına yazı dizimizde kullandığımız projemiz üzerinde geliştirme yapacağız.

Herşeyden önce uygulamada şifremi unuttum mekanizmasını inşa edebilmek için token provider yapılanmasını servis olarak dahil etmemiz gerekmektedir. Bunun için “Startup.cs” dosyasına giderek 21. satırda olduğu gibi “AddDefaultTokenProviders” metodunu eklememiz yeterlidir.

    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>()
              .AddDefaultTokenProviders(); 

            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?}"));
        }
    }

Bu temel konfigürasyondan sonra kullanıcının ihtiyaca binaen yeniden şifre talep edebilmesi için “Şifremi Unuttum” sayfasını tasarlayalım.

Şifremi Unuttum Sayfasının Tasarlanması

İlk olarak uygulamamızın “Models” -> “ViewModels” klasörü altında yeni şifre talebi esnasında veriyi karşılayabilmek için “ResetPasswordViewModel” isminde bir viewmodel tasarlıyoruz.

    public class ResetPasswordViewModel
    {
        [Display(Name = "E-Posta Adresiniz")]
        [Required(ErrorMessage = "Lütfen e-posta adresinizi boş geçmeyiniz.")]
        [EmailAddress(ErrorMessage = "Lütfen uygun formatta e-posta giriniz.")]
        public string Email { get; set; }
    }

Ardından “User(Controller).cs” controller sınıfı içerisinde ilgili talebi yöneteceğimiz “PasswordReset” isimli bir action oluşturuyoruz ve aşağıdaki gibi geliştiriyoruz.

    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 PasswordReset()
        {
            return View();
        }
        [HttpPost]
        public async Task<IActionResult> PasswordReset(ResetPasswordViewModel model)
        {
            AppUser user = await _userManager.FindByEmailAsync(model.Email);
            if (user != null)
            {
                string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);

                MailMessage mail = new MailMessage();
                mail.IsBodyHtml = true;
                mail.To.Add(user.Email);
                mail.From = new MailAddress("******@gmail.com", "Şifre Güncelleme", System.Text.Encoding.UTF8);
                mail.Subject = "Şifre Güncelleme Talebi";
                mail.Body = $"<a target=\"_blank\" href=\"https://localhost:5001{Url.Action("UpdatePassword", "User", new { userId = user.Id, token = HttpUtility.UrlEncode(resetToken) })}\">Yeni şifre talebi için tıklayınız</a>";
                mail.IsBodyHtml = true;
                SmtpClient smp = new SmtpClient();
                smp.Credentials = new NetworkCredential("*****@gmail.com", "******");
                smp.Port = 587;
                smp.Host = "smtp.gmail.com";
                smp.EnableSsl = true;
                smp.Send(mail);

                ViewBag.State = true;
            }
            else
                ViewBag.State = false;

            return View();
        }
        .
        .
        .
        //diğer actionlar
        .
        .
        .
    }

Yukarıdaki kod bloğunda, birazdan view içeriğinide göreceğimiz “PasswordReset” actionı geliştirilmiştir. Yapılan post neticesinde kullanıcı tarafından doğru email adresi girilmişse eğer 20. satırda “GeneratePasswordResetTokenAsync” metodu ile kullanıcıya özel token değeri üretilmektedir. Hemen ardından üretilen bu değer user id değeri ile birlikte oluşturulan bir update linkine query string olarak eklenmekte ve ilgili url yeni şifre talep eden kullanıcının email adresine gönderilmektedir. Dolayısıyla kullanıcı url üzerinden gönderilen token değerinin güvencesiyle kendisine ait hesaba dair şifreyi güncelleyebilecektir.

Evet… Burada email yapılanmasını farklı bir katmanda tasarlamak daha doğru olabilir. Lakin örneklendirmeyi fazla parçalamamak adına olması gereken bu durumu sizlere bırakıyorum…

Şimdide “PasswordReset” actionının view’ini görelim.

@model AspNetCoreIdentityExample.Models.ViewModels.ResetPasswordViewModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h3>Şifremi Unuttum</h3>

<hr />
@if (ViewBag.State == null)
{
    <form asp-action="PasswordReset">
        <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 colspan="3"><input type="submit" value="Yeni Şifre Talebi Gönder" /></td>
            </tr>
        </table>
    </form>
}
else
{
    if (ViewBag.State)
    {
        @:<div class="alert-success">Şifre talebi için e-posta adresinize bilgilerindirici mail gönderilmiştir.</div>
    }
    else
    {
        @:<div class="alert-success">Böyle bir e-posta bulunmamaktadır.</div>
    }
}

Evet, artık şifreyi güncelleme işlemini operasyonel olarak gerçekleştirecek “UpdatePassword” action metodunu tasarlayabiliriz.

Şifreyi Güncelleme Sayfasının Tasarlanması

Kullanıcı tarafımızca mail üzerinden gönderilen url’e tıkladığı vakit yeni şifresini belirleyebilmek için “UpdatePassword” action metodu tarafından karşılanacaktır. Dolayısıyla ilk olarak kullanıcının girdiği şifreyi taşıması için “UpdatePasswordViewModel” isminde bir viewmodel tasarlayalım.

    public class UpdatePasswordViewModel
    {
        [Display(Name = "Yeni Şifre")]
        [Required(ErrorMessage = "Lütfen şifreyi boş geçmeyiniz.")]
        [DataType(DataType.Password)]
        public string Password { get; set; }
    }

Ardından “UpdatePassword” action metodunu inşa edelim.

    public class UserController : Controller
    {
        readonly UserManager<AppUser> _userManager;
        readonly SignInManager<AppUser> _signInManager;
        public UserController(UserManager<AppUser> userManager, SignInManager<AppUser> signInManager)
        {
            _userManager = userManager;
            _signInManager = signInManager;
        }
        [HttpGet("[action]/{userId}/{token}")]
        public IActionResult UpdatePassword(string userId, string token)
        {
            return View();
        }
        [HttpPost("[action]/{userId}/{token}")]
        public async Task<IActionResult> UpdatePassword(UpdatePasswordViewModel model, string userId, string token)
        {
            AppUser user = await _userManager.FindByIdAsync(userId);
            IdentityResult result = await _userManager.ResetPasswordAsync(user, HttpUtility.UrlDecode(token), model.Password);
            if (result.Succeeded)
            {
                ViewBag.State = true;
                await _userManager.UpdateSecurityStampAsync(user);
            }
            else
                ViewBag.State = false;
            return View();
        }
    }
    .
    .
    .
    //diğer actionlar
    .
    .
    .

Görüldüğü üzere kullanıcıya gönderilen e-postadaki urle tıklandığı vakit query string olarak belirtilen userId ve token değerleri “UpdatePassword” action metodu tarafından yakalanmakta ve TempData‘ya atılmaktadır. Post neticesinde “UpdatePasswordViewModel” parametresi kullanıcı tarafından girilen yeni şifreyi taşımaktadır ve 19. satırda olduğu gibi “ResetPasswordAsync” metodu ile token değeri kontrolünde şifre güncellemesi gerçekleştirilmektedir. 23. satırda ise ana değerlerden biri olan şifrenin değişmesinden dolayı “UpdateSecurityStampAsync” metodu aracılığıyla ilgili kullanıcının SecurityStamp kolonu yeni bir değerle güncellenmiştir. Böylece önceki üretilen cookie değerleri çürütülmüştür.

“UpdatePassword” actionının view dosyasınıda incelersek eğer;

@model AspNetCoreIdentityExample.Models.ViewModels.UpdatePasswordViewModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h3>Şifremi Unuttum</h3>

<hr />
@if (ViewBag.State == null)
{
    <form asp-action="UpdatePassword">
        <table>
            <tr>
                <td colspan="3"><div asp-validation-summary="All"></div></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 colspan="3"><input type="submit" value="Şifreyi Güncelle" /></td>
            </tr>
        </table>
    </form>
}
else
{
    if (ViewBag.State)
    {
        @:<div class="alert-success">Şifre başarıyla güncellenmiştir.</div>
    }
    else
    {
        @:<div class="alert-success">Şifreyi güncellerken beklenmeyen hatayla karşılaşıldı.</div>
    }
}

şeklinde olacaktır.

Artık uygulamanın bu son halini kaydedip, derlediğiniz vakit aşağıdaki gibi şifremi unuttum mekanizmasının çalıştığını gözlemleyebilirsiniz.
Asp.NET Core Identity - Şifremi Unuttum - XI

İlgilenenlerin faydalanması dileğiyle.
Sonraki yazılarımda görüşmek üzere.
İyi çalışmalar…

Not : Örnek projeyi indirmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

27 Cevaplar

  1. Şenol dedi ki:

    Merhaba Hocam,

    Şifre yenileme talebi için e-postaya gelen mail’e tıkladığımda
    ” HTTP Error 404.11 – Not Found
    İstek filtreleme modülü çift kaçış sırası içeren bir isteği reddedecek şekilde yapılandırıldı.” hatası alıyorum.
    Nasıl düzeltebilirim.

  2. Şenol dedi ki:

    hocam size gonderdiğim kod engelleniyor heralde

  3. Şenol dedi ki:

    Görüntü kabul etmiyor bu alan.

  4. Şenol dedi ki:

    Mail atmıştım size ordan bakabilir misiniz?

  5. Şenol dedi ki:

    Size gönderdiğim fotoğrafa sığması için böyle yazmıştım. Normalde kod tek satırda yazılı. Örnek URL’e burdan ulaşabilirsiniz:
    https://ibb.co/X8zHQ39

    • Gençay dedi ki:

      Url’i görsel olarak tam görememekteyim. Lütfen metin olarak paylaşınız. Ya da siz URL’de özellikle “+” operatörü ya da benzeri var mı kontrol ediniz. Hatanın sebebi url’de ki bu tarz bir karakterin olması olabilir.

  6. Şenol dedi ki:

    https://localhost:44330/UpdatePassword/94519abf-d22a
    -45ed-88ec-c61e3afdc913/CfDJ8Cv5GzmHKPNMjkS%252byklWmBHnf0x
    cfJZZ0zfRCM9S2yGZLMUmoeSL2YQSJOJ9uJdjhnAYw0cd35Y8k4
    rcEX%252b1VvY09hKlQqLIwMsryPlhNwXfEDmovG6UaC%252fW0rnC
    TPlF55C3%252bwE%252f5iPBSDM9GpiibAYh1L%252fn6LpxTXtl4pJqYb
    %252fWNJKM360QulZD6IJBVep7PUmIZEfQUro0saLx4Uvhdq2qZqvpuSEtNpqccXR%252fGJ1j

  7. dgc dedi ki:

    merhaba hocam bu sorunun çözümü için yardımcı olabilir misiniz?

    • Gençay dedi ki:

      Merhaba,

      Hangi sorunun çözümü için yardım istiyorsunuz?

      • dgc dedi ki:

        Şifre yenileme talebi için e-postaya gelen mail’e tıkladığımda
        ” HTTP Error 404.11 – Not Found
        İstek filtreleme modülü çift kaçış sırası içeren bir isteği reddedecek şekilde yapılandırıldı.” hatasını bende alıyorum ve bu hatanın oluşan url in token değerinde özel karakterlerin bulunmasından kaynaklı olduğu için hata veriyor fakat bu özel karakterleri nasıl ortadan kaldırırım veya nasıl bu şekilde çalışmasına izin verilir bunu bulamadım.

  8. ishak dedi ki:

    Sorunun çözümü :

    byte[] tokenGeneratedBytes = Encoding.UTF8.GetBytes(resetToken);
    var codeEncoded = WebEncoders.Base64UrlEncode(tokenGeneratedBytes);
    

    ve

    var codeDecodedBytes = WebEncoders.Base64UrlDecode(UID);
    var codeDecoded = Encoding.UTF8.GetString(codeDecodedBytes);
    

    bunlar ile değiştirin.

  1. 30 Ağustos 2019

    […] Asp.NET Core Identity – Şifremi Unuttum – XI […]

  2. 01 Eylül 2019

    […] ‘şifremi unuttum‘ senaryolarında kullanıcının şifreyi resetlemesi için üretilen token değerini […]

Bir cevap yazın

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

*