29

Microsoft is coming up with a new Membership system called ASP.NET Identity (also the default in ASP.NET MVC 5). I found the sample project, but this is not implemented a password reset.

On password reset topic just found this Article: Implementing User Confirmation and Password Reset with One ASP.NET Identity – Pain or Pleasure, not help for me, because do not use the built-in password recovery.

As I was looking at the options, as I think we need to generate a reset token, which I will send to the user. The user can set then the new password using the token, overwriting the old one.

I found the IdentityManager.Passwords.GenerateResetPasswordToken / IdentityManager.Passwords.GenerateResetPasswordTokenAsync(string tokenId, string userName, validUntilUtc), but I could not figure out what it might mean the tokenId parameter.

How do I implement the Password Reset in ASP.NET with MVC 5.0?

George Stocker
  • 57,289
  • 29
  • 176
  • 237
Gábor Plesz
  • 1,203
  • 1
  • 17
  • 28
  • 1
    Thank you for your help and support! My problem is that method (IdentityManager.Passwords.GenerateResetPasswordToken) described could not find any information anywhere, so I can not use it. But anyway, I will soon make up for the missing information and i will include valid code! – Gábor Plesz Sep 25 '13 at 15:12
  • so what's the difference between `IdentityManager` and `UserManager`? When I created a new project the `AccountController` used `UserManager`. – Eonasdan Oct 31 '13 at 16:59
  • 1
    I've moved your answer to your answer, instead of it being in the question. – George Stocker Mar 19 '14 at 13:51
  • 2
    See my tutorial Account Confirmation and Password Recovery with ASP.NET Identity http://www.asp.net/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity – RickAndMSFT May 16 '14 at 21:25
  • link does not work anymore... – Cerveser Jul 19 '17 at 18:34

2 Answers2

9

I get it: The tokenid is a freely chosen identity, which identifies a password option. For example,

1. looks like the password recovery process, step 1 (it is based on: https://stackoverflow.com/a/698879/208922)

[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
//[RecaptchaControlMvc.CaptchaValidator]
public virtual async Task<ActionResult> ResetPassword(
                                              ResetPasswordViewModel rpvm)
{
    string message = null;
    //the token is valid for one day
    var until = DateTime.Now.AddDays(1);
    //We find the user, as the token can not generate the e-mail address, 
    //but the name should be.
    var db = new Context();
    var user = db.Users.SingleOrDefault(x=>x.Email == rpvm.Email);

    var token = new StringBuilder();

    //Prepare a 10-character random text
    using (RNGCryptoServiceProvider 
                        rngCsp = new RNGCryptoServiceProvider())
    {
        var data = new byte[4];
        for (int i = 0; i < 10; i++)
        {
            //filled with an array of random numbers
            rngCsp.GetBytes(data);
            //this is converted into a character from A to Z
            var randomchar = Convert.ToChar(
                                      //produce a random number 
                                      //between 0 and 25
                                      BitConverter.ToUInt32(data, 0) % 26 
                                      //Convert.ToInt32('A')==65
                                      + 65
                             );
            token.Append(randomchar);
        }
    }
    //This will be the password change identifier 
    //that the user will be sent out
    var tokenid = token.ToString();

    if (null!=user)
    {
        //Generating a token
        var result = await IdentityManager
                                .Passwords
                                .GenerateResetPasswordTokenAsync(
                                              tokenid, 
                                              user.UserName, 
                                              until
                           );

        if (result.Success)
        {
            //send the email
            ...
        }
    }
    message = 
        "We have sent a password reset request if the email is verified.";
    return RedirectToAction(
                   MVC.Account.ResetPasswordWithToken(
                               token: string.Empty, 
                               message: message
                   )
           );
}

2 And then when the user enters the token and the new password:

[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
//[RecaptchaControlMvc.CaptchaValidator]
public virtual async Task<ActionResult> ResetPasswordWithToken(
                                            ResetPasswordWithTokenViewModel 
                                                        rpwtvm
                                        )
{
    if (ModelState.IsValid)
    {
        string message = null;
        //reset the password
        var result = await IdentityManager.Passwords.ResetPasswordAsync(
                                                   rpwtvm.Token, 
                                                   rpwtvm.Password
                           );
        if (result.Success)
        { 
            message = "the password has been reset.";
            return RedirectToAction(
                        MVC.Account.ResetPasswordCompleted(message: message)
                   );
        }
        else
        {
            AddErrors(result);
        }
    }
    return View(MVC.Account.ResetPasswordWithToken(rpwtvm));
}

Skeleton proposal to sample project on github, if anyone needs it may be tested.The E-mail sending not yet written, possibly with the addition soon.

Community
  • 1
  • 1
Gábor Plesz
  • 1,203
  • 1
  • 17
  • 28
5

Seems like a lot of trouble... What advantage does the above give over:

  1. the user clicking a 'Recover Account' link
  2. this sends an 64 byte encoded string of a datetime ticks value (call it psuedo-hash) in an email
  3. click the link back in the email to a controller/action route that
  4. matches email and it's source server to psuedo-hash, decrypts the psuedo-hash, validates the time since sent and
  5. offers a View for the user to set a new password
  6. with a valid password, the code removes the old user password and assigns the new.
  7. Once complete, successful or not, delete the psuedo-hash.

With this flow, at no time do you EVER send a password out of your domain.

Please, anyone, prove to me how this is any less secure.

Robert Achmann
  • 1,986
  • 3
  • 40
  • 66
  • 10
    I actually wanted to remove this answer, but left it up to see the folly in it's thought process. 1. The Asp.Net password reset process is standardized and proven. 2. There are no passwords sent out over the wire. 3. Why reinvent the wheel? 4. Once you understand how password recovery works in Identity 2.1+, it's so simple, why would you want to bother with developing anything else - including the time needed to design and test your process thoroughly enough to pass security muster. Don't do what my answer suggests - it's just a time waster – Robert Achmann Mar 18 '15 at 13:14