0

I'm making a function to reset the password of a user using ASP.NET Identity. However, the token is not being picked up in my controller. When I submit the form, it says

"The Token field is required."

Why does it not pick up my token? Following code is being used to generate the resetLink:

    var user = _userManager.FindByNameAsync(email).Result;

    if (user == null || !_userManager.IsEmailConfirmedAsync(user).Result)
    {
        ViewBag.Message = "Error while resetting your password!";
        return View("ResetPassword");
    }

    var token = _userManager.
          GeneratePasswordResetTokenAsync(user).Result;

    var resetLink = Url.Action("SetPassword",
                    "Auth", new { token },
                     protocol: HttpContext.Request.Scheme);

This generates the link: http://localhost:50015/adviseur/auth/setpassword?token=someverylongnumbers

This link goes to the following view, viewmodel and controller: AuthController.cs

[HttpGet]
public IActionResult SetPassword()
{
    return View();
}

SetPassword.cshtml

@model SetPasswordModel

<h1>Set Your Password</h1>

<form asp-controller="Auth"
      asp-action="RequestNewPassword"
      method="post">

    <input type="hidden" asp-for="Token" />
    <table>
        <tr>
            <td><label asp-for="UserName"></label></td>
            <td><input asp-for="UserName" /></td>
        </tr>
        <tr>
            <td>
                <label asp-for="Password">
                    New Password
                </label>
            </td>
            <td><input asp-for="Password" /></td>
        </tr>
        <tr>
            <td>
                <label asp-for="ConfirmPassword">
                    Confirm New Password
                </label>
            </td>
            <td><input asp-for="ConfirmPassword" /></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit"
                       value="Reset Password" />
            </td>
        </tr>
    </table>

    <div asp-validation-summary="All"></div>

</form>

SetPasswordModel.cs:

#region Using

using System.ComponentModel.DataAnnotations;

#endregion

namespace x.Areas.Adviseur.ViewModels
{
    public class SetPasswordModel
    {
        [Required]
        public string UserName { get; set; }
        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }
        [Required]
        [DataType(DataType.Password)]
        public string ConfirmPassword { get; set; }
        [Required]
        public string Token { get; set; }
    }
}

The form makes the request to the following controller:

[HttpPost]
public IActionResult RequestNewPassword(SetPasswordModel obj)
{
    User user = _userManager.FindByNameAsync(obj.UserName).Result;
    IdentityResult result = _userManager.ResetPasswordAsync(user, obj.Token, obj.Password).Result;

    if (result.Succeeded)
    {
        ViewBag.Message = "Password reset successful!";
        return View("Login");
    }
    else
    {
        ViewBag.Message = "Error while resetting the password!";
        return View("SetPassword");
    }
}
Some Name
  • 561
  • 6
  • 18
  • Is the token in your `obj` an empty string or null or is it actually the token? – Marco Jan 31 '21 at 16:44
  • @Marco It doesn't even reach the controller, it seems to be an issue in the view/viewmodel. – Some Name Jan 31 '21 at 16:52
  • The SetPassword.cshtml must have a get action method, that must return this view. Add that to your question please, because that is what generates the view model. – Marco Jan 31 '21 at 16:53
  • @Marco added the controller that contains the view – Some Name Jan 31 '21 at 16:56
  • 1
    Your generated link contains token query variable this means you can handle it from controller like : `public IActionResult SetPassword([FromQuery]string token) {...` Otherwise token is always be null. – Batuhan Jan 31 '21 at 17:10
  • Does the error appear in your browser? – Marco Jan 31 '21 at 19:11
  • 1
    Please consider making your actions `async` and using `await` instead of using `.Result` everywhere you call asynchronous methods. See [Get result from async method](https://stackoverflow.com/q/20884604/215552) and [How to justify using await instead of .Result() or .Wait() in .NET Core?](https://softwareengineering.stackexchange.com/q/398998/222246) on [softwareengineering.se]. – Heretic Monkey Feb 01 '21 at 17:45

1 Answers1

1

the token is not being picked up in my controller

That is because you haven't send it. You are not collecting the token from the url to use it on the next step. Therefore, at the RequestNewPassword action method, the model binder cannot find a value in the Token form-field, and it cannot create a SetPasswordModel object without a value for the Token property because you have marked it as Required. Hence the complain -

"The Token field is required."

At your SetPassword action method -

  1. Add a string parameter named token to the method, so that the model binder can collect the token string from the url
  2. Pass the token to the view through the ViewBag or the model.
[HttpGet]
public IActionResult SetPassword(string token)
{
    ViewBag.Token = token;
    return View();

    // OR,
    // var model = new SetPasswordModel { Token = token };
    // return View(model);
}

If you pass the token through the model, then no change is needed in the view. But if you pass it through the ViewBag, then set it to the Token form-field -

<input type="hidden" asp-for="Token" value="@ViewBag.Token" />

Now, when you submit the form, at RequestNewPassword action method, the model binder will be able to create a SetPasswordModel object for you without complaining.

atiyar
  • 7,762
  • 6
  • 34
  • 75
  • I didn't even need the ViewBag.Token = token, but adding it in the constructor did work. Thanks! – Some Name Feb 01 '21 at 20:02
  • @SomeName I updated the answer. Right now its working for you because the name of the method parameter and the form-field are same in lower case (`token`). But that might not always be case. So, you **should** always explicitly pass the token to the view. – atiyar Feb 01 '21 at 21:16
  • Ah, makes sense. Good to know. Thanks for the update! – Some Name Feb 02 '21 at 22:19