Enable Password Resetting with Simple Membership in MVC 4

来源:互联网 发布:实时监测网速软件 编辑:程序博客网 时间:2024/05/16 15:39

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
原创粉丝点击