I'm working on adding reset password functionality on our K12 site. I have created a simple controller and view that accepts the userid and reset token as parameters and displays a simple password/confirm password view to reset the users password. The URL is sent to the user via a email template. This is in a area called Coordinators. I'm using IIS 10 on local and dev.
This works locally when testing but when loaded to the dev server I get a 404 error. I have used RouteDebugger and can see the route on the dev site, but it will not give route information on a 404 page. The route works locally though.
I have added a second controller/view as a test incase there is a issue loading a backend resource. This controller just displays the text "Index". I get the same result. It works locally but fails on the dev server. Interesting note: If I log into the dev sever and access the page it works, but using the same URL from my local PC fails with the 404 error.
Local and the dev site point to the same Kentico database and the web.config files match in both environments.
I am missing something but I haven't been able to figure it out. Do I need to do something with Kentico or is this an IIS issue?
Below are examples of the code.
reset path:
... /Coordinators/resetpassword/Reset?userId=3779&token=AQAAANCMnd8BFd ...
Route class:
public class CoordinatorsAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Coordinators";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Coordinator Search",
"Coordinators/search",
new { controller = "Search", action = "Index", searchText = UrlParameter.Optional, pageNumber = UrlParameter.Optional, indexes = "BCIndex;BCFileIndex" }
);
context.MapRoute(
"Coordinator_Login",
"Coordinators/login/{action}",
new { controller = "login", action = "Index" }
);
context.MapRoute(
"Coordinator_Logout",
"Coordinators/logout/{action}",
new { controller = "Logout", action = "Index" }
);
context.MapRoute(
"Coordinator_Register",
"Coordinators/register/{action}",
new { controller = "register", action = "Index" }
);
context.MapRoute(
"Coordinator_Reset_Password",
"Coordinators/resetpassword/{action}",
new { controller = "ResetPassword", action = "Index" }
);
context.MapRoute(
"Coordinator_Forgot_Password",
"Coordinators/forgotpassword/{action}",
new { controller = "ForgotPassword", action = "Index" }
);
context.MapRoute(
"Coordinator_UnlockAccount",
"Coordinators/unlockaccount/{action}",
new { controller = "UnlockAccount", action = "Index" }
);
context.MapRoute(
"Coordinator_Profile",
"Coordinators/profile/{action}",
new { controller = "profile", action = "Index" }
);
context.MapRoute(
"Coordinator_Directory",
"Coordinators/user-directory/{action}",
new { controller = "directory", action = "Index" }
);
context.MapRoute(
"Coordinator_Default",
"Coordinators/{controller}/{action}"
);
}
}
Controller class:
public class ResetPasswordController : BaseController
{
public IAccountManager _accountManager { get; set; }
public ILocalizationService _localizationService { get; }
public ResetPasswordController(IAccountManager accountManager,
IBusinessDependencies dependencies)
: base(dependencies)
{
_accountManager = accountManager ?? throw new ArgumentNullException(nameof(accountManager));
_localizationService = dependencies.LocalizationService ?? throw new ArgumentNullException(nameof(dependencies.LocalizationService));
}
[AllowAnonymous]
[HttpGet]
public ActionResult Index()
{
ResetPasswordViewModel model = new ResetPasswordViewModel();
return View(model);
}
[AllowAnonymous]
[HttpGet]
public async Task<ActionResult> Reset(int? userId, string token)
{
var model = new ResetPasswordViewModel();
IdentityManagerResult<ResetPasswordResultState, ResetPasswordViewModel> result = new IdentityManagerResult<ResetPasswordResultState, ResetPasswordViewModel>();
try
{
// Verifies the parameters of the password reset request
// True if the token is valid for the specified user, false if the token is invalid or has expired
// By default, the generated tokens are single-use and expire in 1 day
result = await _accountManager.VerifyResetPasswordTokenAsync(userId ?? 1, token);
if (result.Success)
{
// If the password request is valid, displays the password reset form
model = result.Data;
}
else
{
// If the password request is invalid, returns a view informing the user
model.Status = _localizationService.Localize("membership.passwreset.failed");
}
}
catch (InvalidOperationException)
{
// An InvalidOperationException occurs if a user with the given ID is not found
// Returns a view informing the user that the password reset request is not valid
model.Status = _localizationService.Localize("membership.passwreset.failed");
}
return View("Reset", model);
}
[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Reset(ResetPasswordViewModel model)
{
IdentityManagerResult<ResetPasswordResultState> result = new IdentityManagerResult<ResetPasswordResultState>();
// Validates the received password data based on the view model
if (ModelState.IsValid)
{
// setup the default status message
result.Success = false;
result.ResultState = ResetPasswordResultState.PasswordNotReset;
model.Status = _localizationService.Localize("membership.passwordreset.failed");
try
{
result = await _accountManager.ResetPasswordAsync(model);
// Changes the user's password if the provided reset token is valid
if (result.Success)
{
// If the password change was successful, displays a view and email informing the user
var emailMessage = new EmailMessage();
var macroResolver = MacroResolver.GetInstance();
emailMessage.Recipients = model.UserName;
EmailSender.SendEmail(SiteContext.CurrentSiteName, emailMessage, "Membership.PasswordReset", macroResolver, false);
model.Status = _localizationService.Localize("membership.passwordreset.success").Replace("%username%", model.UserName);
return Redirect("/coordinators/login");
}
else
{
// Occurs if the reset token is invalid
// Returns a view informing the user that the password reset failed
model.Status = _localizationService.Localize("membership.passwordreset.failed");
}
}
catch (InvalidOperationException)
{
// An InvalidOperationException occurs if a user with the given ID is not found
// Returns a view informing the user that the password reset failed
model.Status = _localizationService.Localize("membership.passwordreset.failed");
}
}
return View(model);
}
}
View class(Reset.cshtml):
@model #####.Areas.Coordinators.Models.ResetPasswordViewModel
@{ Layout = "~/Areas/Coordinators/Views/Shared/_Layout.cshtml"; }
<div class="container">
@using (Html.BeginForm())
{
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div><span class="required-field"></span> - denotes a required field</div>
@Html.AntiForgeryToken()
<input type="hidden" name="ResetToken" value="@Model.ResetToken" />
<input type="hidden" name="UserName" value="@Model.UserName" />
<div class="form-group">
@Html.LabelFor(m => m.Password) @if (ModelMetadata.FromStringExpression("UserName", ViewData).IsRequired)
{<span class='required-field'></span>}
@Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
</div>
<div class="form-group">
@Html.LabelFor(m => m.PasswordVerify) @if (ModelMetadata.FromStringExpression("UserName", ViewData).IsRequired)
{<span class='required-field'></span>}
@Html.ValidationMessageFor(m => m.PasswordVerify, "", new { @class = "text-danger" })
@Html.PasswordFor(m => m.PasswordVerify, new { @class = "form-control" })
</div>
<div>
<button type="submit" class="btn btn-green">Submit</button>
</div>
<div class="form-group">
<span class="">@Html.DisplayFor(m => m.Status)</span>
</div>
}
</div>