Restablecimiento de contraseña de identidad de ASP.NET

95

¿Cómo puedo obtener la contraseña de un usuario en el nuevo sistema de identidad ASP.NET? ¿O cómo puedo restablecer sin conocer el actual (el usuario olvidó la contraseña)?

daniel
fuente

Respuestas:

102

En versión actual

Suponiendo que ha manejado la verificación de la solicitud para restablecer la contraseña olvidada, use el siguiente código como pasos de código de muestra.

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);

En AspNet Nightly Build

El marco se actualiza para trabajar con Token para manejar solicitudes como ForgetPassword. Una vez en el lanzamiento, se espera una guía de código simple.

Actualizar:

Esta actualización es solo para proporcionar pasos más claros.

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
fuente
¿Sabes cuándo se lanzará la versión 1.1?
graycrow
Todavía está en alfa y se acaba de lanzar 1.0. Así que suponga muchos meses. myget.org/gallery/aspnetwebstacknightly
jd4u
11
Curiosamente, la llamada al método store.SetPasswordHashAsync (cUser, hashedNewPassword) no funcionó para mí, en su lugar tuve que configurar manualmente cUser.PasswordHash = hashedNewPassword y luego llamar a UserManager.UpdateAsync (usuario);
Andy Mehalick
1
El código que no funciona solo es posible si el contexto de recuperación del usuario y el contexto de la tienda son diferentes. El código era solo un ejemplo de pasos, no era exacto. Pronto actualizará la respuesta para evitar este problema para otros.
jd4u
1
El Marco 1 no lo proporciona. Pero Framework 2-alpha tiene pocas características que pueden proporcionar un proceso simple para manejar solicitudes de restablecimiento de contraseña. aspnetidentity.codeplex.com
jd4u
138

¿O cómo puedo restablecer sin conocer el actual (el usuario olvidó la contraseña)?

Si desea cambiar una contraseña utilizando el UserManager pero no desea proporcionar la contraseña actual del usuario, puede generar un token de restablecimiento de contraseña y luego usarlo inmediatamente.

string resetToken = await UserManager.GeneratePasswordResetTokenAsync(model.Id);
IdentityResult passwordChangeResult = await UserManager.ResetPasswordAsync(model.Id, resetToken, model.NewPassword);
Daniel Wright
fuente
8
Esta es, con mucho, la mejor y más limpia forma de establecer una nueva contraseña. El problema con la respuesta aceptada es que pasa por alto las validaciones de complejidad de la contraseña al acceder directamente al hash de contraseña.
Chris
6
Para su información, puede recibir el error "No hay un IUserTokenProvider registrado". si usa la lógica anterior. Vea esto stackoverflow.com/questions/22629936/… .
Prasad Kanaparthi
1
Esto funciona solo para Microsoft.AspNet.Identity en la versión 2, supongo. No puede encontrar el método GeneratePasswordResetTokenAsync en la versión 1.
romanoza
Gracias por su respuesta. Funciona de maravilla para mí.
Thomas.Benz
4
Si obtiene un token no válido , asegúrese de que el SecurityStampde su usuario no sea nulo. Esto puede suceder para usuarios migrados desde otras bases de datos o usuarios que no fueron creados a través del UserManager.CreateAsync()método.
Alisson
70

Obsoleto

Esta fue la respuesta original. Funciona, pero tiene un problema. ¿Y si AddPasswordfalla? El usuario se queda sin contraseña.

La respuesta original: podemos usar tres líneas de código:

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

userManager.RemovePassword(userId);

userManager.AddPassword(userId, newPassword);

Véase también: http://msdn.microsoft.com/en-us/library/dn457095(v=vs.111).aspx

Ahora recomendado

Probablemente sea mejor usar la respuesta que propuso EdwardBrey y luego DanielWright elaboró ​​más tarde con una muestra de código.

Shaun Luttin
fuente
1
Gracias a Dios por esto, pensé que tendría que crear una nueva tienda de usuarios hasta que vi esto.
Lucas
¿Hay alguna forma de hacer esto directamente en SQL? Me encantaría entregarle a mi DBA un sproc para llamar cuando sea necesario en lugar de un ejecutable.
Mark Richman
@MarkRichman Esa es una nueva pregunta. Sin embargo, una cosa que puede hacer es inspeccionar el T-SQL generado que se ejecuta en SQL Server.
Shaun Luttin
3
Tenga cuidado con esto, siempre que AddPassword falle (es decir, complejidad de contraseña insuficiente), el usuario se quedará sin contraseña.
Chris
1
Bueno, el enfoque más limpio sin pasar por alto ninguna regla comercial (porque cuando accede al hash de contraseña directamente no hay validación de complejidad de contraseña) es lo que ha propuesto Daniel Wright.
Chris
29

En su UserManager, primero llame a GeneratePasswordResetTokenAsync . Una vez que el usuario haya verificado su identidad (por ejemplo, al recibir el token en un correo electrónico), pase el token a ResetPasswordAsync .

Edward Brey
fuente
2
Tratando de averiguar por qué ResetPasswordAsync requiere una identificación de usuario y una forma razonable de obtenerla del usuario cuando aparece con un token. GeneratePasswordReset usa un token que tiene más de 150 caracteres ... parece que eso sería suficiente para almacenar criptográficamente una identificación de usuario para que no tenga que implementar eso yo mismo. :(
pettys
Supongo que está solicitando la identificación de usuario para que pueda ingresar el token de reinicio en la base de datos de identidad con esa identificación de usuario. Si no hiciera esto, ¿cómo sabría el marco si el token era válido? Debería poder extraer el ID de usuario mediante User.Identity.GetUserId () o similar.
Ryan Buddicom
1
Requerir la identificación de usuario es una elección tonta por parte de la API, el token ya está en la base de datos cuando se llama a ResetPassword (async) y debería ser suficiente con validarlo con la entrada.
Filip
@Filip, la ventaja de ResetPasswordAsynctomar una ID de usuario es que el proveedor de identidad solo necesita indexar las ID de usuario, no también tokens. Esto le permite escalar mejor si hay muchos usuarios.
Edward Brey
2
@Edward Brey bueno, ¿cómo se obtiene la identificación de usuario para la llamada de reinicio?
Filip
2
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);
}

Este fragmento de código se extrae del proyecto AspNetIdentitySample disponible en github

Sclarson
fuente
2

Crear método en 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
fuente
2

La mejor manera de restablecer la contraseña en el uso de Asp.Net Core Identity para API web.

Nota * : Error () y Result () se crean para uso interno. Puedes devolver lo que quieras.

        [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
fuente
2
Esto también funcionó para mí con Fx v 4.5. La otra solución no funcionó. Básicamente, esto también fue mucho más simple. Ni siquiera necesita obtener el usuario, ya que todos los métodos aceptarán la identificación. Solo lo necesitaba para un reinicio temporal único en mi interfaz de administración, por lo que no necesitaba todas las verificaciones de errores.
Steve Hiner
1

En caso de restablecimiento de contraseña, se recomienda restablecerla enviando un token de restablecimiento de contraseña al correo electrónico del usuario registrado y pedirle al usuario que proporcione una nueva contraseña. Si ha creado una biblioteca .NET fácilmente utilizable sobre el marco de identidad con los ajustes de configuración predeterminados. Puede encontrar detalles en el enlace del blog y el código fuente en github.

Rahul Garg
fuente
1

Creo que la guía de Microsoft para ASP.NET Identity es un buen comienzo.

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

Nota:

Si no usa AccountController y no desea restablecer su contraseña, use Request.GetOwinContext().GetUserManager<ApplicationUserManager>();. Si no tiene el mismo OwinContext, debe crear uno nuevo DataProtectorTokenProvidercomo el que OwinContextusa. Por defecto mira App_Start -> IdentityConfig.cs. Debería verse algo parecido new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));.

Podría crearse así:

Sin 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 = "[email protected]";

    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 = "[email protected]";
    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);
}

Con Owin:

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

    var email = "[email protected]";

    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 = "[email protected]";
    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);
}

El DpapiDataProtectionProvidery DataProtectorTokenProviderdebe crearse con el mismo nombre para que funcione un restablecimiento de contraseña. Usar Owin para crear el token de restablecimiento de contraseña y luego crear uno nuevo DpapiDataProtectionProvidercon otro nombre no funcionará.

Código que uso para 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
fuente
1

Hice una pequeña investigación y la solución que me funcionó fue una combinación de algunas soluciones fundadas en esta publicación.

Básicamente, estoy compilando esta solución y estoy publicando lo que funciona para mí. En mi caso, no quiero usar ningún token de .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);

}
Después
fuente