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

Daniel
fonte

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);
jd4u
fonte
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);
Daniel Wright
fonte
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.

Shaun Luttin
fonte
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 .

Edward Brey
fonte
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

Sclarson
fonte
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);
}
tmg
fonte
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.

Rahul Garg
fonte
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 = "[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);
}

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

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; }
}
Ogglas
fonte
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);

}
AFetter
fonte
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);
            }
        }
Manish Vadher
fonte
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