5

I'm attempting to migrate my MVC4 application from ASP.NET Membership to the new SimpleMembership and have run into a snag with password resets. In my Login view, I have "Forgot Password" hyperlink that does an AJAX post to an action that sends an email with a reset password hyperlink (based loosely on MSDN guidelines):

[HttpPost]
[AllowAnonymous]
public ActionResult ForgotPassword(string userName)
{
    //get the user from database, validate it exists, etc.
    var token = WebSecurity.GeneratePasswordResetToken(userName);
    var resetLink = "<a href='" + Url.Action("ResetPassword", "Account", new {un = userName, rt = token}, "http") + "'>Reset Password</a>";
    //send the email
    return Json(new {result = "Email sent."});
}

This works fine, but for some reason when I attempt to use the Reset Password link, I get redirected to the Login screen and Fiddler shows a 401-Unauthorized. I've tried copy/pasting the link into the browser address bar as well as creating a junk HTML file with the anchor tag in it, but the result is still a 401. Here's the Reset Password action (also in the Account controller from the MVC4 template):

[HttpPost]
[AllowAnonymous]
public ActionResult ResetPassword(string un, string rt)
{
    var model = new ResetPasswordViewModel {UserName = un, ResetToken = rt};
    return View(model);
}

I've tried removing the HttpPost attribute as well, but that also yields a 401. What am I missing here?

UPDATE
Thanks to Steve's comment, I fully-qualified the HttpPost attribute and have now realized that it's attempting to post to an area instead of straight to the root Account controller. Using RouteDebugger, I see a resource not found error:

Matched Route: {controller}/{action}/{id}
Generated URL: /SiteRoot/Reports/Account/ResetPassword?un=whatever&rt=removedForSecurity using the route "Reports/{controller}/{action}/{id}"

Note that it is looking in the Reports area. So, I changed my anchor tag construction in the ForgotPassword action, adding the Area specification in the route values object, as follows:

var resetLink = "<a href='" + Url.Action("ResetPassword", "Account", new {un = userName, rt = token, Area = ""}, "http") + "'>Reset Password</a>";

But it's still attempting to use the Reports area. How can I assure that the anchor tag in the email will direct to the site root instead of an area?

UPDATE 2
I removed the HttpPost attribute, and am now getting the original 401 redirect.

UPDATE 3 ResetPassword view code:

@model Whatever.Web.Models.ResetPasswordViewModel
@{
    ViewBag.Title = "Reset Password";
}
@using (Html.BeginForm())
{
    <fieldset>
        <legend>Reset Password</legend>
        <table>
            <tr>
                <td>User name:</td>
                <td>@Html.TextBoxFor(m => m.UserName, new { @class="disabled-textbox", @readonly="readonly", autocomplete="off" })</td>
                <td></td>
            </tr>
            <tr>
                <td>New Password:</td>
                <td>@Html.PasswordFor(m => m.NewPassword, new { id = "resetPasswordNewPassword" })</td>
                <td></td>
            </tr>
            <tr>
                <td>Confirm New Password:</td>
                <td>@Html.Password("resetPasswordConfirmPassword", "", new { id = "resetPasswordConfirmPassword"})</td>
                <td>
                    <div id="passwordsMatch"></div>
                </td>
            </tr>
        </table>
        <input type="submit" id="submitButton" value="Submit" disabled="disabled"/>
        <div id="resetPasswordResultDiv"></div>
    </fieldset>
}
<script type="text/javascript">
    $(document).ready(function() {
        $("#resetPasswordNewPassword, #resetPasswordConfirmPassword").keyup(function() {
            if ($("#resetPasswordNewPassword").val().length > 0 && $("#resetPasswordConfirmPassword").val().length > 0 && $("#resetPasswordNewPassword").val() != $("#resetPasswordConfirmPassword").val()) {
                if ($("#resetPasswordNewPassword").val() != $("#resetPasswordConfirmPassword").val()) {
                    $("#passwordsMatch").html('<span style="color: red; font-weight: bold;>Passwords do not match</span>');
                    $("#submitButton").attr('disabled', true);
                } else {
                    $("#submitButton").removeAttr('disabled');
                }
            } else {
                $("#passwordsMatch").html('');
                $("#submitButton").attr('disabled', true);
            }
        });
    });
</script>

UPDATE 4
I tried adding another action method to the Account controller that results in a "hello world" style view. Added [AllowAnonymous] attribute, of course. This method also results in a 401 redirect to login.

AJ.
  • 16,368
  • 20
  • 95
  • 150
  • 1
    There are two AllowAnonmyousAttribute classes. Are you sure you're using the one from `System.Web.Mvc` and not `System.Web.Http` ? – Steve's a D Jan 22 '13 at 16:58
  • @Steve - IntelliSense seems to think it's the right one (`System.Web.Mvc`). I tried fully qualifying it in the attribute `[System.Web.Mvc.HttpPost]` and it gets a resource not found error. I'll post an update in a minute. – AJ. Jan 22 '13 at 17:19
  • Also, it looks like you're just creating a link and sending it in an email. What makes you believe you should be using `[HttpPost]` and not `[HttpGet]`? Also, using `var` is typically bad practice unless you're delcaring anonymous types – Steve's a D Jan 22 '13 at 17:27
  • @Steve - I do not agree that using `var` is bad practice unless declaring anonymous types, although I could easily be persuaded if you provide a source. – AJ. Jan 22 '13 at 17:35
  • I was just clarifying that you can't use it with HttpPost. It wont **ever** hit that action method. – Steve's a D Jan 22 '13 at 17:42
  • Does your view have an `Html.RenderAction` which is trying to render an action that does not have the `[AllowAnonymous]` attribute? (either on the view, or the _Layout?) – Steve's a D Jan 22 '13 at 18:04
  • Nope - edited the OP with the view code. – AJ. Jan 22 '13 at 18:08
  • @AJ. thanks in advance... could you please share the resetpasswordviewmodel fields that you are using in your example?. Brgds!! – s_h Mar 15 '13 at 12:49
  • probably public string ResetToken { get; set; } / public string NewPassword { get; set; } / public string ConfirmPassword { get; set; } ?? brgds! – s_h Mar 15 '13 at 12:52

2 Answers2

2

I don't see any problems with the way you've got this set up. There has to be something else going on here. Check your _Layout.cshmtl file and assure that you don't have ANY calls to other actions that could be causing the 401.

This person had a similar problem: https://stackoverflow.com/a/11046666/199913

Community
  • 1
  • 1
Dean
  • 1,550
  • 2
  • 15
  • 22
  • OMG, you were right! I totally forgot that I have a `RenderAction` call in my _Layout file that produces a sidebar menu on every page of the site. I added `[AllowAnonymous]` to that controller and now it works. Thank you!! – AJ. Jan 23 '13 at 15:32
  • I'd chang it to not render the menu (or at least parts of it) when not authenticated but this's the same issue I ran into in my app as well. Contextual information or some sort of pseudo MVC callstack (what controller, action and/or view) is very useful in MVC apps. – Nick Albrecht Jan 23 '13 at 15:39
0

Pretty sure Area="" should be area=""

Steve's a D
  • 3,801
  • 10
  • 39
  • 60
  • I removed the HttpPost and, with the new link construction (specifying `area = ""`, lower-case a) I get the same result as I was originally getting: 401 redirect. I've cleared my cache in all browsers as well. This is vexing! Thanks for your continued assistance. – AJ. Jan 22 '13 at 17:47
  • @AJ I have no idea, I'm just taking shots in the dark here. – Steve's a D Jan 22 '13 at 17:53