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 {
get;
set; }
[
Required]
[
StringLength(100, ErrorMessage =
"The {0} must be at least {2} characters long.", MinimumLength = 6)]
[
DataType(
DataType.Password)]
[
Display(Name =
"Password")]
public
string Password {
get;
set; }
[
DataType(
DataType.Password)]
[
Display(Name =
"Confirm password")]
[
Compare(
"Password", ErrorMessage =
"The password and confirmation password do not match.")]
public
string ConfirmPassword {
get;
set; }
//new properties
[
Required]
[
Display(Name=
"Email ID")]
public
string EmailId {
get;
set; }
[
Required]
[
Display(Name =
"About Yourself")]
public
string Details {
get;
set; }
}
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 {
get;
set; }
//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 {
get;
set; }
public
string UserName {
get;
set; }
//new properties
public
string EmailId {
get;
set; }
public
string Details {
get;
set; }
}
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 {
get;
set; }
//newly added
public
DbSet<
webpages_Membership> webpages_Memberships {
get;
set; }
}
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 {
get;
set; }
public
DateTime CreateDate {
get;
set; }
public
string ConfirmationToken {
get;
set; }
public
bool IsConfirmed {
get;
set; }
public
DateTime LastPasswordFailureDate {
get;
set; }
public
int PasswordFailuresSinceLastSuccess {
get;
set; }
public
string Password {
get;
set; }
public
DateTime PasswordChangeDate {
get;
set; }
public
string PasswordSalt {
get;
set; }
public
string PasswordVerificationToken {
get;
set; }
public
DateTime PasswordVerificationTokenExpirationDate {
get;
set; }
}
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.