124

How can I get the password of a user in the new ASP.NET Identity system? Or how can I reset without knowing the current one (user forgot password)?

daniel
  • 34,281
  • 39
  • 104
  • 158

10 Answers10

178

Or how can I reset without knowing the current one (user forgot password)?

If you want to change a password using the UserManager but you do not want to supply the user's current password, you can generate a password reset token and then use it immediately instead.

string resetToken = await UserManager.GeneratePasswordResetTokenAsync(model.Id);
IdentityResult passwordChangeResult = await UserManager.ResetPasswordAsync(model.Id, resetToken, model.NewPassword);
Daniel Wright
  • 1,781
  • 1
  • 10
  • 5
  • 10
    This is by far the best and cleanest way to set a new password. The problem with the accepted answer is that it bypasses the password complexity validations by directly accessing the password hasher. – Chris Apr 17 '15 at 18:05
  • 8
    Fyi, You may get error 'No IUserTokenProvider is registered.' if you get use above logic. See this http://stackoverflow.com/questions/22629936/no-iusertokenprovider-is-registered. – Prasad Kanaparthi Aug 30 '15 at 12:30
  • 1
    This works for Microsoft.AspNet.Identity in version 2 only, I suppose. You can't find the GeneratePasswordResetTokenAsync method in version 1. – romanoza Jan 13 '16 at 11:22
  • I don't understand how you prompt the user for the new password. How can you verify the reset token without providing the new password as a parameter? Why is there not a separate method to validate the reset token so that you can prompt the user for a new password on a form so they can type their new password, and then reset it? – Robert Noack Nov 18 '17 at 01:51
  • 5
    If you get **Invalid Token**, make sure the `SecurityStamp` for your user is not null. This may happen for users migrated from other databases, or users which were not created through `UserManager.CreateAsync()` method. – Alisson Reinaldo Silva Jul 18 '18 at 02:49
  • @Alisson I'm using MongoDB, and i create user in different ways, ... how can i use token provider... reset password fails for me, and it seem, you are the only one pointing to my issue – Hassan Faghihi Aug 17 '19 at 05:26
116

In current release

Assuming you have handled the verification of the request to reset the forgotten password, use following code as a sample code steps.

ApplicationDbContext =new ApplicationDbContext()
String userId = "<YourLogicAssignsRequestedUserId>";
String newPassword = "<PasswordAsTypedByUser>";
ApplicationUser cUser = UserManager.FindById(userId);
String hashedNewPassword = UserManager.PasswordHasher.HashPassword(newPassword);
UserStore<ApplicationUser> store = new UserStore<ApplicationUser>();            
store.SetPasswordHashAsync(cUser, hashedNewPassword);

In AspNet Nightly Build

The framework is updated to work with Token for handling requests like ForgetPassword. Once in release, simple code guidance is expected.

Update:

This update is just to provide more clear steps.

ApplicationDbContext context = new ApplicationDbContext();
UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(context);
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(store);
String userId = User.Identity.GetUserId();//"<YourLogicAssignsRequestedUserId>";
String newPassword = "test@123"; //"<PasswordAsTypedByUser>";
String hashedNewPassword = UserManager.PasswordHasher.HashPassword(newPassword);                    
ApplicationUser cUser = await store.FindByIdAsync(userId);
await store.SetPasswordHashAsync(cUser, hashedNewPassword);
await store.UpdateAsync(cUser);
jd4u
  • 5,789
  • 2
  • 28
  • 28
  • do you know when version 1.1 will be released? – graycrow Oct 23 '13 at 09:11
  • Its still in alpha, and 1.0 is just released. So assume many months. https://www.myget.org/gallery/aspnetwebstacknightly – jd4u Oct 23 '13 at 11:37
  • 11
    Oddly the store.SetPasswordHashAsync(cUser, hashedNewPassword) method call did not work for me, instead I had to manually set cUser.PasswordHash = hashedNewPassword and then call UserManager.UpdateAsync(user); – Andy Mehalick Oct 26 '13 at 18:25
  • 1
    Code not working is only possible if the User retrieval context and store context are different. The code was only a sample steps, not accurate. Will soon update the answer to avoid this issue for others. – jd4u Oct 27 '13 at 07:14
  • Excellent. Your updated code is working perfectly for me. The only catch is it has to be inside an async method, which is fine for what I am doing. Thanks again! – DigiOz Multimedia Nov 09 '13 at 18:09
  • "_Assuming you have handled the verification of the request to reset the forgotten password_". Does ASP.NET identity provide this functionality or do we have to write it from scratch. Do you have any references to examples of how to do this? – Teevus Mar 15 '14 at 15:04
  • 1
    Framework 1 does not provide. But Framework 2-alpha does have few features that can provide simple process for handling password reset requests. http://aspnetidentity.codeplex.com/ – jd4u Mar 18 '14 at 19:01
  • You got what I wanted. I put your code in an Action Result with [Authorize] tag, and at bottom I put "Response.Write("password changed for: " + cUser.NickName . . . . .then Return null. So, then since controller looks for query string first, I put parameters for userId and newPassword. Of course, I will have to be able to get the user's id from the database but I now have the ability to change a password for a friend who's computer is broken (on my website of course) :::::::::: public async Task ChangePassword(string userId, string newOne) { //jd4u code here – JustJohn Jul 28 '16 at 03:51
  • yep, password not updated until calling `store.UpdateAsync` – Jeremy Ray Brown Feb 22 '19 at 22:47
71

Deprecated

This was the original answer. It does work, but has a problem. What if AddPassword fails? The user is left without a password.

The original answer: we can use three lines of code:

UserManager<IdentityUser> userManager = 
    new UserManager<IdentityUser>(new UserStore<IdentityUser>());

userManager.RemovePassword(userId);

userManager.AddPassword(userId, newPassword);

See also: http://msdn.microsoft.com/en-us/library/dn457095(v=vs.111).aspx

Now Recommended

It's probably better to use the answer that EdwardBrey proposed and then DanielWright later elaborated with a code sample.

Community
  • 1
  • 1
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • 1
    Thank god for this, I thought I'd have to create a new user store until I saw this! – Luke Dec 18 '14 at 14:05
  • Is there any way to do this directly in SQL? I'd love to hand my DBA a sproc to call when needed instead of an executable. – Mark Richman Feb 24 '15 at 19:19
  • @MarkRichman That's a new question. One thing you could do, though, is to inspect the generated T-SQL that runs on SQL Server. – Shaun Luttin Feb 25 '15 at 16:00
  • 3
    Watch out with this on, whenever AddPassword fails (ie insufficient password complexity), the user will be left without a password. – Chris Apr 15 '15 at 20:18
  • @Chris This question and its answers try to address that: http://stackoverflow.com/questions/29291366/asp-net-identity-change-password/29292056#29292056 Either create a custom `ChangePassword` method or use the existing `AddPassword` and keep trying until success. – Shaun Luttin Apr 15 '15 at 20:54
  • 1
    Well the cleanest approach without bypassing any business rules (because when you access the password hasher directly there is no password complexity validation) is what Daniel Wright has proposed. – Chris Apr 17 '15 at 17:58
  • @Chris Good call. I updated my answer to recommend Edward's answer (added 11 months prior to Daniel's.) – Shaun Luttin Apr 17 '15 at 18:25
30

On your UserManager, first call GeneratePasswordResetTokenAsync. Once the user has verified his identity (for example by receiving the token in an email), pass the token to ResetPasswordAsync.

Edward Brey
  • 40,302
  • 20
  • 199
  • 253
  • 3
    Trying to figure out why ResetPasswordAsync requires a user ID, and a reasonable way to get it from the user when they show up with a token. GeneratePasswordReset uses a token that's over 150 chars... seems like that'd be enough to cryptographically stow a user id so I don't have to implement that myself. :( – pettys Apr 17 '15 at 20:36
  • I assume It's asking for the user ID so it can enter the reset token into the Identity Database against that user ID. If it didn't do this how would the framework ever know if the token was valid. You should be able to pull the users ID using User.Identity.GetUserId() or similar. – Ryan Buddicom May 04 '15 at 06:07
  • 1
    Requiring the user id is a silly choice on the API's part, the token is already in the database when ResetPassword(async) is called and it should be enough just to validate it against the input. – Filip Jul 30 '15 at 14:15
  • @Filip, the advantage of `ResetPasswordAsync` taking a user ID is that the identity provider only needs to index user IDs, not also tokens. This lets it scale better if there are many users. – Edward Brey Jul 30 '15 at 15:38
  • @Edward Brey so...more roundtrips = better scalability? – Filip Jul 30 '15 at 15:51
  • @Filip, Extra round trip? You get a token with `GeneratePasswordResetTokenAsync`, email it to the user, wait for the user to give it back via a web browser, and then call `ResetPasswordAsync`. `ResetPasswordAsync`, does the equivalent of `UPDATE Users SET Password = @newPassword WHERE UserId = @userId AND Token = @token`. I don't see where an extra round trip comes in? – Edward Brey Jul 30 '15 at 15:58
  • 2
    @Edward Brey well, how do you fetch the user id for the reset call? – Filip Jul 30 '15 at 16:00
  • @Filip, as far as I know, they expect you to send the user ID along with the token in the email. It certainly does get verbose, adding more to an URL already quite long due to the size of the token. – Edward Brey Jul 30 '15 at 16:12
6

Best way to Reset Password in Asp.Net Core Identity use for Web API.

Note* : Error() and Result() are created for internal use. You can return you want.

        [HttpPost]
        [Route("reset-password")]
        public async Task<IActionResult> ResetPassword(ResetPasswordModel model)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);
            try
            {
                if (model is null)
                    return Error("No data found!");


                var user = await _userManager.FindByIdAsync(AppCommon.ToString(GetUserId()));
                if (user == null)
                    return Error("No user found!");

                Microsoft.AspNetCore.Identity.SignInResult checkOldPassword =
                    await _signInManager.PasswordSignInAsync(user.UserName, model.OldPassword, false, false);

                if (!checkOldPassword.Succeeded)
                    return Error("Old password does not matched.");

                string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
                if (string.IsNullOrEmpty(resetToken))
                    return Error("Error while generating reset token.");

                var result = await _userManager.ResetPasswordAsync(user, resetToken, model.Password);

                if (result.Succeeded)
                    return Result();
                else
                    return Error();
            }
            catch (Exception ex)
            {
                return Error(ex);
            }
        }
Manish Vadher
  • 1,524
  • 15
  • 14
  • 2
    This worked for me with Fx v 4.5 as well. The other solution did not work. Fundamentally this was much simpler too. You don't even really need to get the user since all the methods will accept the id. I just needed it for a temporary one-off reset in my admin interface so I didn't need all the error checks. – Steve Hiner Aug 25 '20 at 01:44
3
string message = null;
//reset the password
var result = await IdentityManager.Passwords.ResetPasswordAsync(model.Token, model.Password);
if (result.Success)
{
    message = "The password has been reset.";
    return RedirectToAction("PasswordResetCompleted", new { message = message });
}
else
{
    AddErrors(result);
}

This snippet of code is taken out of the AspNetIdentitySample project available on github

sclarson
  • 4,362
  • 3
  • 32
  • 44
3

I think Microsoft guide for ASP.NET Identity is a good start.

https://learn.microsoft.com/en-us/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity

Note:

If you do not use AccountController and wan't to reset your password, use Request.GetOwinContext().GetUserManager<ApplicationUserManager>();. If you dont have the same OwinContext you need to create a new DataProtectorTokenProvider like the one OwinContext uses. By default look at App_Start -> IdentityConfig.cs. Should look something like new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));.

Could be created like this:

Without Owin:

[HttpGet]
[AllowAnonymous]
[Route("testReset")]
public IHttpActionResult TestReset()
{
    var db = new ApplicationDbContext();
    var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
    var provider = new DpapiDataProtectionProvider("SampleAppName");
    manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
        provider.Create("SampleTokenName"));

    var email = "test@test.com";

    var user = new ApplicationUser() { UserName = email, Email = email };

    var identityUser = manager.FindByEmail(email);

    if (identityUser == null)
    {
        manager.Create(user);
        identityUser = manager.FindByEmail(email);
    }

    var token = manager.GeneratePasswordResetToken(identityUser.Id);
    return Ok(HttpUtility.UrlEncode(token));
}

[HttpGet]
[AllowAnonymous]
[Route("testReset")]
public IHttpActionResult TestReset(string token)
{
    var db = new ApplicationDbContext();
    var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
    var provider = new DpapiDataProtectionProvider("SampleAppName");
    manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
        provider.Create("SampleTokenName"));
    var email = "test@test.com";
    var identityUser = manager.FindByEmail(email);
    var valid = Task.Run(() => manager.UserTokenProvider.ValidateAsync("ResetPassword", token, manager, identityUser)).Result;
    var result = manager.ResetPassword(identityUser.Id, token, "TestingTest1!");
    return Ok(result);
}

With Owin:

[HttpGet]
[AllowAnonymous]
[Route("testResetWithOwin")]
public IHttpActionResult TestResetWithOwin()
{
    var manager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();

    var email = "test@test.com";

    var user = new ApplicationUser() { UserName = email, Email = email };

    var identityUser = manager.FindByEmail(email);

    if (identityUser == null)
    {
        manager.Create(user);
        identityUser = manager.FindByEmail(email);
    }

    var token = manager.GeneratePasswordResetToken(identityUser.Id);
    return Ok(HttpUtility.UrlEncode(token));
}

[HttpGet]
[AllowAnonymous]
[Route("testResetWithOwin")]
public IHttpActionResult TestResetWithOwin(string token)
{
    var manager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();

    var email = "test@test.com";
    var identityUser = manager.FindByEmail(email);
    var valid = Task.Run(() => manager.UserTokenProvider.ValidateAsync("ResetPassword", token, manager, identityUser)).Result;
    var result = manager.ResetPassword(identityUser.Id, token, "TestingTest1!");
    return Ok(result);
}

The DpapiDataProtectionProvider and DataProtectorTokenProvider needs to be created with the same name for a password reset to work. Using Owin for creating the password reset token and then creating a new DpapiDataProtectionProvider with another name won't work.

Code that I use for ASP.NET Identity:

Web.Config:

<add key="AllowedHosts" value="example.com,example2.com" />

AccountController.cs:

[Route("RequestResetPasswordToken/{email}/")]
[HttpGet]
[AllowAnonymous]
public async Task<IHttpActionResult> GetResetPasswordToken([FromUri]string email)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    var user = await UserManager.FindByEmailAsync(email);
    if (user == null)
    {
        Logger.Warn("Password reset token requested for non existing email");
        // Don't reveal that the user does not exist
        return NoContent();
    }

    //Prevent Host Header Attack -> Password Reset Poisoning. 
    //If the IIS has a binding to accept connections on 80/443 the host parameter can be changed.
    //See https://security.stackexchange.com/a/170759/67046
    if (!ConfigurationManager.AppSettings["AllowedHosts"].Split(',').Contains(Request.RequestUri.Host)) {
            Logger.Warn($"Non allowed host detected for password reset {Request.RequestUri.Scheme}://{Request.Headers.Host}");
            return BadRequest();
    }

    Logger.Info("Creating password reset token for user id {0}", user.Id);

    var host = $"{Request.RequestUri.Scheme}://{Request.Headers.Host}";
    var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
    var callbackUrl = $"{host}/resetPassword/{HttpContext.Current.Server.UrlEncode(user.Email)}/{HttpContext.Current.Server.UrlEncode(token)}";

    var subject = "Client - Password reset.";
    var body = "<html><body>" +
               "<h2>Password reset</h2>" +
               $"<p>Hi {user.FullName}, <a href=\"{callbackUrl}\"> please click this link to reset your password </a></p>" +
               "</body></html>";

    var message = new IdentityMessage
    {
        Body = body,
        Destination = user.Email,
        Subject = subject
    };

    await UserManager.EmailService.SendAsync(message);

    return NoContent();
}

[HttpPost]
[Route("ResetPassword/")]
[AllowAnonymous]
public async Task<IHttpActionResult> ResetPasswordAsync(ResetPasswordRequestModel model)
{
    if (!ModelState.IsValid)
        return NoContent();

    var user = await UserManager.FindByEmailAsync(model.Email);
    if (user == null)
    {
        Logger.Warn("Reset password request for non existing email");
        return NoContent();
    }            

    if (!await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, UserManager, user))
    {
        Logger.Warn("Reset password requested with wrong token");
        return NoContent();
    }

    var result = await UserManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);

    if (result.Succeeded)
    {
        Logger.Info("Creating password reset token for user id {0}", user.Id);

        const string subject = "Client - Password reset success.";
        var body = "<html><body>" +
                   "<h1>Your password for Client was reset</h1>" +
                   $"<p>Hi {user.FullName}!</p>" +
                   "<p>Your password for Client was reset. Please inform us if you did not request this change.</p>" +
                   "</body></html>";

        var message = new IdentityMessage
        {
            Body = body,
            Destination = user.Email,
            Subject = subject
        };

        await UserManager.EmailService.SendAsync(message);
    }

    return NoContent();
}

public class ResetPasswordRequestModel
{
    [Required]
    [Display(Name = "Token")]
    public string Token { get; set; }

    [Required]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 10)]
    [DataType(DataType.Password)]
    [Display(Name = "New password")]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm new password")]
    [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418
2

Create method in UserManager<TUser, TKey>

public Task<IdentityResult> ChangePassword(int userId, string newPassword)
{
     var user = Users.FirstOrDefault(u => u.Id == userId);
     if (user == null)
          return new Task<IdentityResult>(() => IdentityResult.Failed());

     var store = Store as IUserPasswordStore<User, int>;
     return base.UpdatePassword(store, user, newPassword);
}
tmg
  • 19,895
  • 5
  • 72
  • 76
2

In case of password reset, it is recommended to reset it through sending password reset token to registered user email and ask user to provide new password. If have created a easily usable .NET library over Identity framework with default configuration settins. You can find details at blog link and source code at github.

Rahul Garg
  • 4,069
  • 1
  • 34
  • 31
2

I did a little investigation and the solution that works for me was a mix of a few solutions founded in this post.

I'm basically compiling this solution and I'm posting what works for me. In my case, I'm don't want to use any token from .net core.

public async Task ResetPassword(string userId, string password)
{
    var user = await _userManager.FindByIdAsync(userId);
    var hashPassword= _userManager.PasswordHasher.HashPassword(user, password);
    user.PasswordHash = passwordHash;
    await _userManager.UpdateAsync(user);

}
AFetter
  • 3,355
  • 6
  • 38
  • 62
  • "what works for me" is just not good enough for something security relevant. I want to use as much premade .NET Core as possible. – Heinzlmaen Oct 09 '20 at 14:23