I want to implement the "forgot password" functionality in Asp.Net MVC 5. Here is the code flow:
- Take user email ID
- Let the user enter the token received in his email inbox
- If the token matches then redirect for password reset page
- Set the new password
Please tell me where I can improve.
public ActionResult ForgotPassword()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ForgotPassword(ForgotPasswordMV viewModel)
{
if (ModelState.IsValid)
{
if (SecurityHelper.SendEmail(viewModel.Email))
return RedirectToAction("VerifyToken", new { email = viewModel.Email });
ModelState.AddModelError("Email", "Email not found");
}
return View();
}
public ActionResult VerifyToken(string email = null)
{
VerifyTokenMV viewModel = new VerifyTokenMV
{
Email = email,
Token = ""
};
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult VerifyToken(VerifyTokenMV viewModel)
{
if (ModelState.IsValid)
{
if (SecurityHelper.VerifyToken(viewModel.Token, viewModel.Email))
return RedirectToAction("ConfirmPassword", new { email = viewModel.Email });
ModelState.AddModelError("Token", "Token does not match");
}
return View();
}
public ActionResult ConfirmPassword(string email = null)
{
ConfirmPasswordMV viewModel = new ConfirmPasswordMV
{
EmailID = email
};
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmPassword(ConfirmPasswordMV viewModel)
{
if (ModelState.IsValid)
{
if (viewModel.Password == viewModel.ConfirmPassword)
{
SecurityHelper.UpdatePassword(viewModel.EmailID, viewModel.Password);
TempData["Message"] = "Your password has been updated.";
return RedirectToAction(action, homeController);
}
ModelState.AddModelError("", "Password does not match.");
}
return View();
}
View Model
public class ConfirmPasswordMV
{
[Display(Name = "Enter your new password"), Required]
public string Password { get; set; }
[Display(Name = "Confirm Password"), Required]
public string ConfirmPassword { get; set; }
public string EmailID { get; set; }
}
public class VerifyTokenMV
{
[Display(Name = "Enter Verification Token sent to your email"), Required]
public string Token { get; set; }
public string Email { get; set; }
}
public class ForgotPasswordMV
{
[Display(Name = "Enter your email:"), Required]
public string Email { get; set; }
}
Views
@using (@Html.BeginForm("VerifyToken", "Security"))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(m => m.Email)
@Html.ValidationSummary(true, "Please fix below errors")
<div class="form-group">
@Html.LabelFor(m => m.Token)
@Html.TextBoxFor(m => m.Token, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Token)
</div>
<button class="btn btn-primary">Continue</button>
}
View 2
@using (@Html.BeginForm("ConfirmPassword", "Security"))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(m => m.EmailID)
@Html.ValidationSummary(true, "Please fix below errors")
<div class="form-group">
@Html.LabelFor(m => m.Password)
@Html.TextBoxFor(m => m.Password, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Password)
</div>
<div class="form-group">
@Html.LabelFor(m => m.ConfirmPassword)
@Html.TextBoxFor(m => m.ConfirmPassword, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.ConfirmPassword)
</div>
<button class="btn btn-primary">Save Password</button>
}
View 3
<p>@ViewBag.ErrorMessage</p>
@using (@Html.BeginForm("ForgotPassword", "Security"))
{
@Html.ValidationSummary(true, "Please fix below error")
@Html.AntiForgeryToken()
<div class="form-group">
@Html.LabelFor(m => m.Email)
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Email)
</div>
<button class="btn btn-primary">Continue</button>
}
Note
- Kindly note that I am passing email to every single screen as hidden field and thus in controller too so that I can verify the correct user. I have passed in using an anonymous object. It's my novice attempt, so please tell me the flaws/improvements I can make.
- Can I have just one view and make all of these as partial view?
- Do I really need three separate view models for these pages?
- I do not have direct interaction with models as code hits API written in VB.NET code.
EDIT
I have three columns in DB a. token b. emailId c. TimeStamp
I expire token if time is more than 5 minutes.