Senha de redefinição de identidade ASP.NET


95

Como posso obter a senha de um usuário no novo sistema de identidade ASP.NET? Ou como posso reiniciar sem saber o atual (usuário esqueceu a senha)?

Respostas:


102

Na versão atual

Supondo que você tenha feito a verificação da solicitação para redefinir a senha esquecida, use o código a seguir como etapas de código de amostra.

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

No AspNet Nightly Build

A estrutura é atualizada para funcionar com Token para lidar com solicitações como ForgetPassword. Uma vez no lançamento, espera-se uma orientação de código simples.

Atualizar:

Esta atualização é apenas para fornecer etapas mais claras.

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

você sabe quando a versão 1.1 será lançada?
— graycrow

Ainda está em alpha e 1.0 acaba de ser lançado. Portanto, suponha que muitos meses. myget.org/gallery/aspnetwebstacknightly
— jd4u

11
Estranhamente, a chamada do método store.SetPasswordHashAsync (cUser, hashedNewPassword) não funcionou para mim, em vez disso, tive que definir manualmente cUser.PasswordHash = hashedNewPassword e, em seguida, chamar UserManager.UpdateAsync (usuário);
— Andy Mehalick

1
O código que não funciona só é possível se o contexto de recuperação do usuário e o contexto de armazenamento forem diferentes. O código era apenas uma amostra de etapas, não era preciso. Em breve atualizará a resposta para evitar esse problema para outras pessoas.
— jd4u

1
A Estrutura 1 não fornece. Mas o Framework 2-alpha tem poucos recursos que podem fornecer um processo simples para lidar com solicitações de redefinição de senha. aspnetidentity.codeplex.com
— jd4u

138

Ou como posso reiniciar sem saber o atual (usuário esqueceu a senha)?

Se você deseja alterar uma senha usando o UserManager, mas não deseja fornecer a senha atual do usuário, pode gerar um token de redefinição de senha e usá-lo imediatamente.

string resetToken = await UserManager.GeneratePasswordResetTokenAsync(model.Id);
IdentityResult passwordChangeResult = await UserManager.ResetPasswordAsync(model.Id, resetToken, model.NewPassword);

8
Esta é de longe a melhor e mais limpa maneira de definir uma nova senha. O problema com a resposta aceita é que ela ignora as validações de complexidade de senha acessando diretamente o hasher de senha.
— Chris

6
Fyi, você pode obter o erro 'Nenhum IUserTokenProvider está registrado.' se você conseguir usar a lógica acima. Consulte stackoverflow.com/questions/22629936/… .
— Prasad Kanaparthi

1
Isso funciona para Microsoft.AspNet.Identity na versão 2 apenas, suponho. Você não pode encontrar o método GeneratePasswordResetTokenAsync na versão 1.
— romanoza

Obrigado pela sua resposta. Funciona como um encanto para mim.
— Thomas.Benz

4
Se você obtiver um token inválido , certifique-se de que SecurityStamppara o seu usuário não seja nulo. Isso pode acontecer para usuários migrados de outros bancos de dados ou usuários que não foram criados por meio do UserManager.CreateAsync()método.
— Alisson

70

Descontinuada

Esta foi a resposta original. Funciona, mas tem um problema. E se AddPasswordfalhar? O usuário fica sem senha.

A resposta original: podemos usar três linhas de código:

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

userManager.RemovePassword(userId);

userManager.AddPassword(userId, newPassword);

Consulte também: http://msdn.microsoft.com/en-us/library/dn457095(v=vs.111).aspx

Agora recomendado

Provavelmente é melhor usar a resposta que EdwardBrey propôs e DanielWright posteriormente elaborado com um exemplo de código.


1
Graças a Deus por isso, pensei que teria que criar uma nova loja de usuários até ver isso!
— Lucas

Existe alguma maneira de fazer isso diretamente no SQL? Adoraria entregar ao meu DBA um sproc para chamar quando necessário, em vez de um executável.
— Mark Richman

@MarkRichman Essa é uma nova questão. Uma coisa que você pode fazer, no entanto, é inspecionar o T-SQL gerado que é executado no SQL Server.
— Shaun Luttin

3
Cuidado com isso ligado, sempre que AddPassword falhar (ou seja, complexidade insuficiente da senha), o usuário ficará sem senha.
— Chris,

1
Bem, a abordagem mais limpa sem ignorar as regras de negócios (porque quando você acessa o hasher de senha diretamente, não há validação de complexidade de senha) é o que Daniel Wright propôs.
— Chris,

29

No seu UserManager, primeiro chame GeneratePasswordResetTokenAsync . Depois que o usuário verificar sua identidade (por exemplo, recebendo o token em um e-mail), passe o token para ResetPasswordAsync .


1
Tentar descobrir por que ResetPasswordAsync requer um ID de usuário e uma maneira razoável de obtê-lo do usuário quando ele aparecer com um token. GeneratePasswordReset usa um token com mais de 150 caracteres ... parece que isso seria o suficiente para armazenar criptograficamente um ID de usuário para que eu não precise implementá-lo sozinho. :(
— pettys

Presumo que esteja solicitando o ID do usuário para que possa inserir o token de redefinição no banco de dados de identidade com relação ao ID do usuário. Se não fizesse isso, como a estrutura saberia se o token era válido. Você deve conseguir obter o ID do usuário usando User.Identity.GetUserId () ou semelhante.
— Ryan Buddicom

1
Exigir o ID do usuário é uma escolha boba da parte da API, o token já está no banco de dados quando ResetPassword (async) é chamado e deve ser suficiente apenas para validá-lo com a entrada.
— Filip

@Filip, a vantagem de ResetPasswordAsyncobter um ID de usuário é que o provedor de identidade só precisa indexar IDs de usuário, não também tokens. Isso permite uma melhor escalabilidade se houver muitos usuários.
— Edward Brey

1
@Edward Brey bem, como você busca o ID do usuário para a chamada de reinicialização?
— 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 snippet de código foi retirado do projeto AspNetIdentitySample disponível no github


2

Método de criação em 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);
}

1

Em caso de redefinição de senha, recomenda-se redefini-la enviando um token de redefinição de senha para o e-mail do usuário cadastrado e solicitar ao usuário a nova senha. Se você criou uma biblioteca .NET facilmente utilizável sobre a estrutura de identidade com os ajustes de configuração padrão. Você pode encontrar detalhes no link do blog e o código-fonte no github.


1

Acho que o guia da Microsoft para identidade do ASP.NET é um bom começo.

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

Nota:

Se você não usa AccountController e não deseja redefinir sua senha, use Request.GetOwinContext().GetUserManager<ApplicationUserManager>();. Se você não tem o mesmo OwinContext, você precisa criar um novo DataProtectorTokenProvidercomo o que OwinContextusa. Por padrão, olhe para App_Start -> IdentityConfig.cs. Deve ser parecido comnew DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity")); .

Pode ser criado assim:

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

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

O DpapiDataProtectionProvidere DataProtectorTokenProviderprecisa ser criado com o mesmo nome para que uma redefinição de senha funcione. Usando Owin para criar o token de redefinição de senha e, em seguida, criar um novoDpapiDataProtectionProvider com outro nome não funcionará.

Código que uso para identidade ASP.NET:

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

1

Eu fiz uma pequena investigação e a solução que funcionou para mim foi uma mistura de algumas soluções encontradas neste post.

Estou basicamente compilando essa solução e postando o que funciona para mim. No meu caso, não quero usar nenhum token do núcleo .net.

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

}

1

Melhor maneira de redefinir a senha no uso de identidade do núcleo Asp.Net para Web API.

Nota * : Erro () e Resultado () são criados para uso interno. Você pode voltar que quiser.

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

1
Isso funcionou para mim com o Fx v 4.5 também. A outra solução não funcionou. Fundamentalmente, isso também era muito mais simples. Você nem mesmo precisa obter o usuário, pois todos os métodos aceitarão o id. Eu só precisava disso para uma redefinição temporária única na minha interface de administrador, então não precisei de todas as verificações de erro.
— Steve Hiner
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.