Enable Password Resetting with Simple Membership in MVC 4

http://www.itorian.com/2013/03/PasswordResetting.html

In this article you will learn how to enable password resetting (in case user forgot the password) with Simple Membership in MVC. User needs to type his username and system will check its existence, if found correct this will send an email containing dynamically generated URL with username and password reset token.

Before start with coding, let’s look at quick demo on YouTube.


This article is going to be quite long, so I will write it in two sections:

i) Sending Password Reset Information via Email
ii) Receiving Password Reset Information from URL

Both section will have step by step approach to help you understand which thing to do first.

Before following the steps given below, create a new MVC 4 Application with Internet Application template and then try running and creating a new user account, this will generate Simple Membership Database Tables.

i) Sending Password Reset Information via Email

Step 1

As you know, we don’t see an email field in a user registration form as well as in the membership database. But we can enable this using some quick changes in the application. Let’s make some changes in DB.


I added two new fields EmailId and Details.

Step 2

Now I want above newly added field’s information to be filled by user when he register on the website so, I need to update Register View Model as well as Register View. Here is the updated Register View Model.

public  class  RegisterModel
{
    [ Required]
    [ Display(Name =  "User name")]
     public  string UserName {  getset; }

    [ Required]
    [ StringLength(100, ErrorMessage =  "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [ DataType( DataType.Password)]
    [ Display(Name =  "Password")]
     public  string Password {  getset; }

    [ DataType( DataType.Password)]
    [ Display(Name =  "Confirm password")]
    [ Compare( "Password", ErrorMessage =  "The password and confirmation password do not match.")]
     public  string ConfirmPassword {  getset; }

     //new properties
    [ Required]
    [ Display(Name= "Email ID")]
     public  string EmailId {  getset; }

    [ Required]
    [ Display(Name =  "About Yourself")]
     public  string Details {  getset; }
}

See (highlighted above), I have added two new properties above to enable strongly typed for Register View. Here it is:

@model MvcMembership.Models. RegisterModel
@{
    ViewBag.Title =  "Register";
}

< hgroup  class ="title">
     < h1 >@ViewBag.Title. </ h1 >
     < h2 >Create a new account. </ h2 >
</ hgroup >

@ using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary()

     < fieldset >
         < legend >Registration Form </ legend >
         < ol >
             < li >
                @Html.LabelFor(m => m.UserName)
                @Html.TextBoxFor(m => m.UserName)
             </ li >
             < li >
                @Html.LabelFor(m => m.Password)
                @Html.PasswordFor(m => m.Password)
             </ li >
             < li >
                @Html.LabelFor(m => m.ConfirmPassword)
                @Html.PasswordFor(m => m.ConfirmPassword)
             </ li >
             < li >
                @Html.LabelFor(m => m.EmailId)
                @Html.TextBoxFor(m => m.EmailId)
             </ li >
             < li >
                @Html.LabelFor(m => m.Details)
                @Html.TextBoxFor(m => m.Details)
             </ li >
         </ ol >
         < input  type ="submit"  value ="Register"  />
     </ fieldset >
}

@section Scripts {
    @ Scripts.Render( "~/bundles/jqueryval")
}

I highlighted the changed made in above code. Now, when user will hit ‘Register’ button on Register view a POST request will happen containing UserName, Password, EmailId, Details.

Step 3

Now, I need to make some changes in POST version of Register controller.

[ HttpPost]
[ AllowAnonymous]
[ ValidateAntiForgeryToken]
public  ActionResult Register( RegisterModel model)
{
     if (ModelState.IsValid)
    {
         // Attempt to register the user
         try
        {
             WebSecurity.CreateUserAndAccount(model.UserName, model.Password,  new { EmailId = model.EmailId, Details = model.Details});
             WebSecurity.Login(model.UserName, model.Password);
             return RedirectToAction( "Index""Home");
        }
         catch ( MembershipCreateUserException e)
        {
            ModelState.AddModelError( "", ErrorCodeToString(e.StatusCode));
        }
    }

     // If we got this far, something failed, redisplay form
     return View(model);
}

I highlighted the changes made in above code. You can see this controller will accept a ‘model’ of type RegisterModel that you can see in step 2.

Please note, this controller will do three things, create a new user account, login and redirect on Index view of Home controller.


So, user is registered now and there is email and details information up in my database.

Step 4

Now, we are ready to implement the password reset functionality for above modified application.

First let’s display a link to user on login page.


When user will click on above ‘Recover’ link, he will be redirect to a new view ‘ForgotPassword’, you need to create this view and its GET and POST methods.

Step 5

Before creating ‘ForgotPassword’ view you need GET and POST version action methods in controller, I will create both in AccountController.

So, the GET version of the action method is here.

[ AllowAnonymous]
public  ActionResult ForgotPassword()
{
     return View();
}

And it will return following view page.

@{
    ViewBag.Title =  "Forgot Password";
}

< h2 >Forgot Password </ h2 >

@ using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
     < fieldset >
         < legend >Forgot Password Form </ legend >
         < ol >
             < li >
                @Html.Label( "User Name"new { @for =  "UserName" })
                @Html.TextBox( "UserName")
                 < span  style =" color: red; ">@TempData[ "Message"] </ span >
             </ li >
         </ ol >
         < input  type ="submit"  value ="Recover"  />
     </ fieldset >
}

That’s it. Notice three things, a TextBox by name ‘UserName’, a <span> to display the message in red color, and a button (input) to submit the UserName with POST request.

So, the POST version of ‘ForgotPassword’ action method which accepts ‘UserName’ from POST request made by above view page is here.

[ HttpPost]
[ AllowAnonymous]
[ ValidateAntiForgeryToken]
public  ActionResult ForgotPassword( string UserName)
{
     //check user existance
     var user =  Membership.GetUser(UserName);
     if (user ==  null)
    {
        TempData[ "Message"] =  "User Not exist.";
    }
     else
    {
         //generate password token
         var token =  WebSecurity.GeneratePasswordResetToken(UserName);
         //create url with above token
         var resetLink =  "<a href='" + Url.Action( "ResetPassword""Account"new { un = UserName, rt = token },  "http") +  "'>Reset Password</a>";
         //get user emailid
         UsersContext db =  new  UsersContext();
         var emailid = ( from i  in db.UserProfiles
                         where i.UserName == UserName
                         select i.EmailId).FirstOrDefault();
         //send mail
         string subject =  "Password Reset Token";
         string body =  "<b>Please find the Password Reset Token</b><br/>" + resetLink;  //edit it
         try
        {
            SendEMail(emailid, subject, body);
            TempData[ "Message"] =  "Mail Sent.";
        }
         catch ( Exception ex)
        {
            TempData[ "Message"] =  "Error occured while sending email." + ex.Message;
        }
         //only for testing
        TempData[ "Message"] = resetLink;
    }
           
     return View();
}

In above action method with the received UserName from POST call I will check for username existence in DB, if not found will display ‘User Not exist.’ with the help of TempData. In case username matches, WebSecurity.GeneratePasswordResetToken will generate a password reset token and put it in the membership database for matching username. After generating password reset token it will generate a URL containing username and password reset token that will be sent via email [Read more in step 6]. Also note, I am using ResetPassword action method of Account controller that we have not implemented so far, you will see it later in this article. At the end I called a method ‘SendMail’ by passing emailed (grabbed from db with linq query), subject and body (containing generated URL) as a parameters.

Step 6

Now, I have URL (containing username and token) to send via email but we don’t have email in scope. So, I created an instance of UserProfile DB Model (you get this automatically when simple membership database comes-up) using UsersContext db = new UsersContext(), you can see it in above POST version of ‘ForgotPassword’ controller.

Here is the DbContext that we get for membership database.

public  class  UsersContext :  DbContext
{
     public UsersContext()
        :  base( "DefaultConnection")
    {
    }

     public  DbSet< UserProfile> UserProfiles {  getset; }
     //newly added
     //public DbSet<webpages_Membership> webpages_Memberships { get; set; }
}

In the Linq query you can see how I’m getting intellisense support for my EmailId and Details field also.


Actually, this is not default, I made some changes in UserProfile View Model, which is here.

[ Table( "UserProfile")]
public  class  UserProfile
{
    [ Key]
    [ DatabaseGeneratedAttribute( DatabaseGeneratedOption.Identity)]
     public  int UserId {  getset; }
     public  string UserName {  getset; }
     //new properties
     public  string EmailId {  getset; }
     public  string Details {  getset; }
}

I highlighted the changes made in above code.


So, far we sent the email containing URL to reset password. Now, user is going to click on the URL we sent via mail and we need to deal with that incoming GET call and verify them by matching username and token (by grabbed data from URL).

ii) Receiving Password Reset Information from URL

Step 1

We need a GET action method by name ‘ResetPassword’ in ‘Account’ controller because we sent this via mail (Read Setp 5 above). Let’s implement it.

[ AllowAnonymous]
public  ActionResult ResetPassword( string un,  string rt)
{
     UsersContext db =  new  UsersContext();
     //TODO: Check the un and rt matching and then perform following
     //get userid of received username
     var userid = ( from i  in db.UserProfiles
                     where i.UserName == un
                     select i.UserId).FirstOrDefault();
     //check userid and token matches
     bool any = ( from j  in db.webpages_Memberships
                 where (j.UserId == userid)
                && (j.PasswordVerificationToken == rt)
                 //&& (j.PasswordVerificationTokenExpirationDate < DateTime.Now)
                 select j).Any();

     if (any ==  true)
    {
         //generate random password
         string newpassword = GenerateRandomPassword(6);
         //reset password
         bool response =  WebSecurity.ResetPassword(rt, newpassword);
         if (response ==  true)
        {
             //get user emailid to send password
             var emailid = ( from i  in db.UserProfiles
                             where i.UserName == un
                             select i.EmailId).FirstOrDefault();
             //send email
             string subject =  "New Password";
             string body =  "<b>Please find the New Password</b><br/>" + newpassword;  //edit it
             try
            {
                SendEMail(emailid, subject, body);
                TempData[ "Message"] =  "Mail Sent.";
            }
             catch ( Exception ex)
            {
                TempData[ "Message"] =  "Error occured while sending email." + ex.Message;
            }

             //display message
            TempData[ "Message"] =  "Success! Check email we sent. Your New Password Is " + newpassword;
        }
         else
        {
            TempData[ "Message"] =  "Hey, avoid random request on this page.";
        }
    }
     else
    {
        TempData[ "Message"] =  "Username and token not maching.";
    }           

     return View();
}

In above code, at very first you can see this method is accepting ‘un’ (which is username) and ‘rt’ (which is password reset token) from the URL.

Then, I created an instance of UserProfile DB Model that will allow me linq query with intellisense.

In the image given below you can see we have UserName in UserProfile Table and PasswordVerificationToken in webpages_Membership Table.


As you know we have UserName and Token (grabbed from URL). So, how will you match both, the best way is by using Stored Procedure but I don’t want to make this long article a book, lol. What I’m going to do is I will get UserId from UserProfile Table (look at first Linq query) and then will match the UserId and Token (look at second Linq query).

The second linq query I’m using ‘db.webpages_Memberships’ as DbSet, so don’t forget to implement it. Here is the DbSet.

public  class  UsersContext :  DbContext
{
     public UsersContext()
        :  base( "DefaultConnection")
    {
    }

     public  DbSet< UserProfile> UserProfiles {  getset; }
     //newly added
     public  DbSet< webpages_Membership> webpages_Memberships {  getset; }
}

I highlighted the changes made in above code. Notice I have used ‘webpages_Memberships’ as a model, we need to implement this too, this section is completely new.

[ Table( "webpages_Membership")]
public  class  webpages_Membership
{
    [ Key]
     public  int UserId {  getset; }
     public  DateTime CreateDate {  getset; }
     public  string ConfirmationToken {  getset; }
     public  bool IsConfirmed {  getset; }
     public  DateTime LastPasswordFailureDate {  getset; }
     public  int PasswordFailuresSinceLastSuccess {  getset; }
     public  string Password {  getset; }
     public  DateTime PasswordChangeDate {  getset; }
     public  string PasswordSalt {  getset; }
     public  string PasswordVerificationToken {  getset; }
     public  DateTime PasswordVerificationTokenExpirationDate {  getset; }
}

Also, the second linq query will return Boolean (variable name is ‘any’) value because I have used .Any() with linq query to check ‘do you have any matching record’, if yes return ‘true’ if no return ‘false’ and show the not matched message immediately.

In case both matches Boolean will have ‘true’ value. Then a random password of length 6 will be generated using a method, given below.

private  string GenerateRandomPassword( int length)
{
     string allowedChars =  "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789!@$?_-*&#+";
     char[] chars =  new  char[length];
     Random rd =  new  Random();
     for ( int i = 0; i < length; i++)
    {
        chars[i] = allowedChars[rd.Next(0, allowedChars.Length)];
    }
     return  new  string(chars);
}

And this generated password will saved in membership database with the help of WebSecurity.ResetPassword method that accepts token and new password. At the same time will get the user’s email id using linq query and send this newly generated password.

Okay, I’m done with everything now, but?

I have used ‘SendEmail’ method twice and the code is here, which is for those who don’t know how to send email.

private  void SendEMail( string emailid,  string subject,  string body)
{
     SmtpClient client =  new  SmtpClient();
    client.DeliveryMethod =  SmtpDeliveryMethod.Network;
    client.EnableSsl =  true;
    client.Host =  "smtp.gmail.com";
    client.Port = 587;

    System.Net. NetworkCredential credentials =  new System.Net. NetworkCredential( "xxxxx@gmail.com", "xxxxx");
    client.UseDefaultCredentials =  false;
    client.Credentials = credentials;

     MailMessage msg =  new  MailMessage();
    msg.From =  new  MailAddress( "xxxxx@gmail.com");
    msg.To.Add( new  MailAddress(emailid));

    msg.Subject = subject;
    msg.IsBodyHtml =  true;
    msg.Body = body;

    client.Send(msg);
}

Hope this helps.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值