Enable Password Resetting with Simple Membership in MVC 4
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("[email protected]", "xxxxx");
client.UseDefaultCredentials = false;
client.Credentials = credentials;
MailMessage msg = new MailMessage();
msg.To.Add(new MailAddress(emailid));
msg.Subject = subject;
msg.IsBodyHtml = true;
msg.Body = body;
client.Send(msg);
}
Hope
this helps.
One more detail - once the password is reset, you need to clear the PasswordVerificationToken from the Membership table. This will prevent unwanted resets, especially in the case of someone's mail getting hacked.
ReplyDeleteagree. WebSecurity.ResetPassword(rt, newpassword) class is smart enough to do this automatically.
DeleteGood post. However a better solution to resetting the password is to send a password reset token and let the user reset the password. This will prevent unauthorised users resetting passwords of other users.
ReplyDeleteI'm also sending the password reset token via mail, if you read this article.
DeleteThank you, I was having trouble using the ResetPassword and this post has the answer that I need to use "token = WebSecurity.GeneratePasswordResetToken(..)" and pass that token in WebSecurity.ResetPassword.
ReplyDeleteThanks for this article, very clear and direct.
ReplyDeleteHi,nice work
ReplyDeletebut i have an error when i test this.
There is already an object named 'webpages_Membership' in the database.
I also have the same problem...
Deleteif I don't add the 'webpages_Membership' to the Context the projects doesn't builed. If I do I can migrate and If i start the project it erases all my data.
I figured it out:
DeleteI stead of adding 'webpages_Membership' to the DAL folder it should be added in the AccountModels.cs
Thank you, I really appreciate your clear explanation of this topic.
ReplyDeleteThanks,excellent Work,
ReplyDeletei have juste a question: Do we Need a vue ResetPassword.chtml??
Great article
ReplyDeleteGood article.
ReplyDeleteBtw, do we have link to the solution we can download from?
I love you
ReplyDeleteCannot implicitly convert type 'long' to 'int?'. An explicit conversion exists (are you missing a cast?)
ReplyDeleteduring the this method WebSecurity.ResetPassword(token, newpassword);
any one tell solution or this
Very nice article... Thanks.
ReplyDeleteI am getting error "Invalid column name 'PasswordChangedDate'" after completing forgot password and reset password. But no error occurs after forgot password. after reset password mainly after creating table webpages_Membership this occurs. it detects error at the line.
ReplyDeletewhat to do now
?
i'm getting the exact same thing. did you ever fix your problem? please help
DeleteHi,
ReplyDeleteNice article. but i have a small doubt. Do we need registerpassowrd.cshtml file. If so kindly share the code.
Thanks for the great post man. I tried this out, but after receiving the New Password email, the program still recognizes the old password and not the new one that is generated randomly and sent with the email. Help
ReplyDeleteCan you post your registerpassword.cshtml code?
DeleteCould you please post the registerpassword.cshtml file
ReplyDeleteHi Abimanyu as i fallow ur entire process as same but i got error like this:The SMTP server requires a secure connection or the client was not authenticated. The server response was: 5.5.1 Authentication Required plz help me
ReplyDeleteI got a problem in SendEmail method is used error is does not exist in current text and another problem is emailId,Detils are invalid coloumns show in register action part plz tell me these two errors
ReplyDeleteRESET PASSWORD VIEW
ReplyDeleteCreate a view called ResetPassword - under the Account folder. I personally made it a partial view.
If your using razor then just add @TempData["Message"] on the view
Save the view and your done ...the return will then display your message
I used it but I am not using the membership provider. I am using my own. When executing "WebSecurity.GeneratePasswordResetToken(username)" It throws error. Please do help me out.
ReplyDeleteBrella from Brasil
ReplyDeleteThanks for the article. Very nice.
Thanks, was very helpful, but I did end up using WebSecurity.ResetPassword, because after the user clicks on the email link, I want them to be able to make a new password.
ReplyDeleteThank you very much :)
ReplyDeleteThanks for the good Post... I have one doubt that in webpages_Membership table column name "PasswordVerificationToken" value is expired at what time ?
ReplyDeletemeans first time it reset the password and send the mail but again when i try to reset the password it throw the error so upto what time i need to wait to reset the password again.
My password reset token displays next to username and does not get sent to my email address?
ReplyDeletegreat articile but can you provide a solution
ReplyDeleteThank you very much! :)
ReplyDeleteError 1 'webpages_Membership': member names cannot be the same as their enclosing type
ReplyDeletei have this error can anyone help me out please
reset password action method is not being called what to do
ReplyDeletePlease share for me your code. I try coding but it not run.
ReplyDeleteThank you so much ! Great and helpful article
ReplyDeleteCan you provide link to download the source code?
ReplyDeletethanx sir
ReplyDeleteThank you. It was nice article. Helped me alot :)
ReplyDeleteThanks brthr for this great article :-)
ReplyDeleteI having an issue when it runs 'No account exists for "UserName", sometimes error didn't get........
what to do ???
For those who want to send reset link to emailid refere "https://www.google.com/settings/security/lesssecureapps" and turn on the access for less secure apps(i.e your application). and make changes in "SendEMail" method as follow:-
ReplyDeleteprivate 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("youremailid", "yourpassword");
client.UseDefaultCredentials = false;
client.Credentials = credentials;
MailMessage msg = new MailMessage();
msg.From = new MailAddress("youremailid");
msg.To.Add(new MailAddress(emailid));
msg.Subject = subject;
msg.IsBodyHtml = true;
msg.Body = body;
client.Send(msg);
}
Unable to send email
ReplyDeletevery helpful article.
ReplyDeletei need WebSecurity.ResetPassword class ?
i need WebSecurity class
ReplyDelete