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.
İ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.
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.
Merhaba,
Mail ile gönderdiğin url’in generate edildiği kodu paylaşır mısın?
hocam size gonderdiğim kod engelleniyor heralde
Sanırım olabilir… Ekran görüntüsünü alıp burada paylaşınız.
Görüntü kabul etmiyor bu alan.
Mail atmıştım size ordan bakabilir misiniz?
Mailiniz gelmedi.
https://tr.imgbb.com/
adresini kullanarak resim yükleyebilir ve verilen linki buradan paylaşabilirsiniz.
Peki… Şimdi bana oluşturulan örnek bir url gönderebilir misiniz?
Ayrıca String Interpolation kullandığınız string işlemlerinde ekstradan + operatörü ile birleştirme yapmamanızı öneririm.
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
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.
Url’de harf, rakam ve % işareti dışında herhangi bir karakter bulunmuyor.
Uygulamayı IIS üzerinden değilde kendi hostu üzerinden çalıştırmayı denediniz mi?
Bu şekilde oldu hocam. IIS’le çalışmasını nasıl sağlayabilirim?
Gün boyu yaptığım tüm araştırmalar ve denemeler neticesinde bir türlü soruna çözüm bulamadım.
https://localhost:44330/UpdatePassword/94519abf-d22a
-45ed-88ec-c61e3afdc913/CfDJ8Cv5GzmHKPNMjkS%252byklWmBHnf0x
cfJZZ0zfRCM9S2yGZLMUmoeSL2YQSJOJ9uJdjhnAYw0cd35Y8k4
rcEX%252b1VvY09hKlQqLIwMsryPlhNwXfEDmovG6UaC%252fW0rnC
TPlF55C3%252bwE%252f5iPBSDM9GpiibAYh1L%252fn6LpxTXtl4pJqYb
%252fWNJKM360QulZD6IJBVep7PUmIZEfQUro0saLx4Uvhdq2qZqvpuSEtNpqccXR%252fGJ1j
Yardımınız için teşekkürler….
Eğer ki çözüm bulursanız buradan paylaşmanızı rica ediyorum.
Kolay gelsin.
merhaba hocam bu sorunun çözümü için yardımcı olabilir misiniz?
Merhaba,
Hangi sorunun çözümü için yardım istiyorsunuz?
Ş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.
Sorunun çözümü :
ve
bunlar ile değiştirin.
Teşekkürler.
Ben teşekkür ederim aslında bu güzel bilgileri bizimle paylaştığınız için.
Çok sağol İshak kardeşim 🙂
Hocam aynı hatayı alıyorum bu code decoded ve encoded ları nerede değiştireceğiz hiç anlamadım projede öyle bi kod parçacığım yok malesef
İshak Beyin şuradaki çözümüyle alakalı sorun yaşıyor olabilirsin. İncelemeni öneririm.
Hocam inceledim fakat anlamadigim nokta şurası bu posttaki şekilde yapilandirdim kodumu fakatnencode ve decode yaptigim bir alan yok ishak bey encode ve decode yapiyor nereye ekleyecegimi bilemedim ishak beyin çöźümünü
Berkay bey aynı problemi bende yaşıyorum . Yani posttaki yapıda İshak beyin çözümünü nerede yapacağımızı bulamadım. Yapan varsa bilgilendirebilir mi?
mrhba efendim bu ıdenty kodunun tamamını git de palaşırmısnz bu mailden cevap yazarsanız sevinirim
Merhaba,
Malesef git kullanmıyorum.
Hocam selamlar,
PasswordReset Actionunda smp.send(mail) kısmında hata almaktayım.
hata şu şekilde;
///////
An unhandled exception occurred while processing the request.
SocketException: Bilinen böyle bir ana bilgisayar yok
System.Net.Dns.InternalGetHostByName(string hostName)
SmtpException: Failure sending mail.
System.Net.Mail.SmtpClient.Send(MailMessage message)
/////
ViewBag.state=true da patlıyor. sanırım bunun nedeni SmtpClient in config tarafında sorun var fakat her bilgi doğru yazdığımı düşünüyorum.
Merhaba,
‘smtp.bilisimkampusu.com’ böyle bir sunucun olduğuna emin misin?
Hocam aynı sorun bende de var smtp.gmail.com bende ki ama aynı sorun devam ediyor
Selamün aleyküm
Kolay gelsin başlığı core olarak açmışsınız ama .net frameworkta kodlar çalışıyormu bilginiz varmı?
Merhaba,
Bu yazı serisinde sadece .NET Core’a uygun bir anlatım sergilenmiştir. .NET Framework’te de çalışmaların benzer niteliklerde sergilenebileceği kanaatindeyim.
mail.Body = $”Yeni şifre talebi için tıklayınız”;
Hocam merhaba,
“GeneratePasswordResetTokenAsync” metodu ile oluşturulan token’ınımız (userId, token,expireTime gibi bilgilerle) bir cookie’de tutuluyor değil mi?
İyi çalışmalar.
Merhaba,
İlgili fonksiyon neticesinde SecurityStamp değeriyle doğrulanabilir bir token oluşturuluyor. Haliyle bizler token’ı doğruladıktan sonra ilgili ‘SecurityStamp’ değerini güncelliyoruz.
Sağolun hocam anladım şimdi.
İyi çalışmalar.
else
ViewBag.State = false;
return View();
BURAYA GELİYOR YADA 404.11
Sorunun çözümü :
1
2
byte[] tokenGeneratedBytes = Encoding.UTF8.GetBytes(resetToken);
var codeEncoded = WebEncoders.Base64UrlEncode(tokenGeneratedBytes);
ve
1
2
var codeDecodedBytes = WebEncoders.Base64UrlDecode(UID);
var codeDecoded = Encoding.UTF8.GetString(codeDecodedBytes);
nedir??
Sa Hocam Bir Sorum Var
Buyrun…
.Net core 5 İdentity içerisinde Resetpassword kullanıyorum mail bağlantısı ile sifre sıfırlama ekranına geliyorum fakat orda şifre sıfırlarken invalid token hatası alıyorum. neden olabilir .
resim ek
merhabalar gençay hocam
konuyu çok güzel anlatmışsınız elinize sağlık
yalnız kafama takılan bir konu var bu email adresine gönderilen token değeri veritabanında yada başka bir yerde depolanmıyormu çünkü ben emaile gelen tokena tıkladığımda action metoda gelen token parametresini emaile gönderilen tokenla karşılaştırması yapılmayacakmı
eğer vaktiniz olurda cevaplarsanız çok memnun olurum
Merhaba,
Hayır, token karşılaştırmasından ziyade o token’ı çözümseyecek değerlere ihtiyacınız olacaktır. Asp.NET Core Identity mekanizmasında bu değer(ler)i
AspNetUsers
tablosundakiSecurityStamp
kolonu sağlamaktadır. Siz,SecurityStamp
değeri ile ürettiğiniz token’ı email üzerinden gönderdikten sonra kullanıcı bu token ile dönüş yapıyorsa tekrarSecurityStamp
ile gelen değeri çözümser ve kontrol edersiniz. Tabi burada bu kontrol süreciniResetPasswordAsync
fonksiyonu üstlenmektedir. Hatta ilgili token doğrulanıp şifre yenilendiği taktirdeUpdateSecurityStampAsync
metodu ile mevcudiyettekiSecurityStamp
değeri güncellenerek üretilmiş token eskitilmiş olur.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ı. Bu şekilde hata alıyorum çözebilen var mı
bu şekilde sorun çözülüyor yinede Bad Request – Invalid URL – HTTP Error 400. hatası alırsanız bilgisayarınızın başlat kısmında arama yerine powershell yazın açılan mavi consoleda Set-ItemProperty -Path HKLM:\System\CurrentControlSet\Services\HTTP\Parameters -Name UrlSegmentMaxLength -Value 2000 -Type “Dword” bu kodu yazıp entera basın daha sonra net stop http yazın kapanınca bide net start http yazın kapandıktan sonra açılmassa bilgisayarı yeniden başlatın otomatik açılıyor ve sorun çözülüyor değerli bilgileri için hocama teşekkür ederim..
yinede çalışmaz da Bad Request – Invalid URL hatası alırsanız başlat çubuğundan arama yerine powershell yazıp açılan komut satırında Set-ItemProperty -Path HKLM:\System\CurrentControlSet\Services\HTTP\Parameters -Name UrlSegmentMaxLength -Value 2000 -Type “Dword” bu kodu yapıştırıp çalıştırın daha sonra yine aynı komut satırında net stop http yazıp enter’a basın kapandıktan sonra net start http yazın ve açın yani aslında yeniden başlatıyoruz oldu ki başlamassa bilgisayarı yeniden başlatın ve artık projeyi açıp denediğinizde sorun çözülmüş olacaktır 🙂
İyi günler Hocam mail gönderimi için kendi kişisel gmail adresimi kullandım fakat smtp hatası oldum hatanın sebebi gmail hesabımda ayarlardan daha az güvenlikli uygulamalara izin ver durumunu açmamam imiş fakat google 30 mayıs 2022 tarihinde bu hizmeti kapatmış yani istesek de bu ayarı kişisel gmail hesabımızda açıp kullanamıyoruz.
Evet, öyle bir problem var. Google dışında farklı bir mail sunucusu kullanmamız gerekiyor.
Merhaba hocam. Ilk once rahatsizlik verdiyim icin ve sorumun garip gele bileceyinden dolayi onceden ozur diliyorum.
Identity-nin simdiki bolumunde kullandigimiz ‘token’ kelmesi tam olarak ne anlama gelmekte? ‘token’ , ‘token yaratmak’ soylediyim zaman aklimda bir sey canlanmiyor. Rica etsem tokenin turkcede tam olarak ne anlama geldiyini soylermisiniz? Tesekkur ederim.
google-da bir kac yere baktim anlamadim, mesela karsima ilk cikan aciklama:
_________________________
Tokens are used in Identity in several ways. You can use them to reset a password or confirm the email address of a user. Here you generate a token specific for the appropriate user which can be used for these two purposes. They will be send to the user, for example as a link to a view which handles the confirmation.
* aciklamanin kaynagi : https://stackoverflow.com/questions/27677345/what-are-a-security-token-and-security-stamp-in-asp-net-identity#:~:text=Tokens%20are%20used%20in%20Identity,view%20which%20handles%20the%20confirmation.
_________________________
* Bu arada “gmail.com” ve “mail.ru”-dan mail atmak bende calismadi, farkli portlar ve saire denedim, olmuyor, maksimum ulastigim sonuc Timeout almak oldu 😀 onun yerine maili Hotmaildan attim usere asagida yazdigim sekilde (arayan olursa benim gibi, boyle yapa bilir). Gmaildan mail ata bilen banada soylerse mutlu olurum.
+ Attigimiz mail userin mailinde ‘Spam’ folderina dusuyor.