Mot de passe de réinitialisation d'identité ASP.NET

95

Comment puis-je obtenir le mot de passe d'un utilisateur dans le nouveau système d'identité ASP.NET? Ou comment puis-je réinitialiser sans connaître l'actuel (mot de passe oublié par l'utilisateur)?

Daniel
la source

Réponses:

102

Dans la version actuelle

En supposant que vous ayez géré la vérification de la demande de réinitialisation du mot de passe oublié, utilisez le code suivant comme exemple d'étapes de code.

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

Dans AspNet Nightly Build

Le cadre est mis à jour pour fonctionner avec Token pour gérer les demandes telles que ForgetPassword. Une fois dans la version, des conseils de code simples sont attendus.

Mettre à jour:

Cette mise à jour est juste pour fournir des étapes plus claires.

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
la source
savez-vous quand la version 1.1 sera publiée?
graycrow
Il est toujours en version alpha et la version 1.0 vient de sortir. Alors supposez plusieurs mois. myget.org/gallery/aspnetwebstacknightly
jd4u
11
Curieusement, l'appel de la méthode store.SetPasswordHashAsync (cUser, hashedNewPassword) ne fonctionnait pas pour moi, au lieu de cela, j'ai dû définir manuellement cUser.PasswordHash = hashedNewPassword, puis appeler UserManager.UpdateAsync (utilisateur);
Andy Mehalick
1
Le code qui ne fonctionne pas n'est possible que si le contexte de récupération de l'utilisateur et le contexte de magasin sont différents. Le code n'était qu'un exemple d'étapes, pas précis. Mettra bientôt à jour la réponse pour éviter ce problème pour les autres.
jd4u
1
Framework 1 ne fournit pas. Mais Framework 2-alpha possède quelques fonctionnalités qui peuvent fournir un processus simple pour gérer les demandes de réinitialisation de mot de passe. aspnetidentity.codeplex.com
jd4u
138

Ou comment puis-je réinitialiser sans connaître l'actuel (mot de passe oublié par l'utilisateur)?

Si vous souhaitez modifier un mot de passe à l'aide de UserManager mais que vous ne souhaitez pas fournir le mot de passe actuel de l'utilisateur, vous pouvez générer un jeton de réinitialisation de mot de passe et l'utiliser immédiatement à la place.

string resetToken = await UserManager.GeneratePasswordResetTokenAsync(model.Id);
IdentityResult passwordChangeResult = await UserManager.ResetPasswordAsync(model.Id, resetToken, model.NewPassword);
Daniel Wright
la source
8
C'est de loin le moyen le meilleur et le plus propre de définir un nouveau mot de passe. Le problème avec la réponse acceptée est qu'elle contourne les validations de complexité de mot de passe en accédant directement au hacheur de mot de passe.
Chris
6
Pour info, vous pouvez obtenir l'erreur "Aucun IUserTokenProvider n'est enregistré." si vous utilisez la logique ci-dessus. Voir ce stackoverflow.com/questions/22629936/… .
Prasad Kanaparthi
1
Cela ne fonctionne que pour Microsoft.AspNet.Identity dans la version 2, je suppose. Vous ne pouvez pas trouver la méthode GeneratePasswordResetTokenAsync dans la version 1.
romanoza
Merci pour votre réponse. Cela fonctionne comme un charme pour moi.
Thomas.Benz
4
Si vous obtenez un jeton non valide , assurez-vous que le SecurityStamppour votre utilisateur n'est pas nul. Cela peut se produire pour les utilisateurs migrés à partir d'autres bases de données ou pour les utilisateurs qui n'ont pas été créés via UserManager.CreateAsync()method.
Alisson
70

Obsolète

C'était la réponse originale. Cela fonctionne, mais a un problème. Et si AddPasswordéchoue? L'utilisateur est laissé sans mot de passe.

La réponse originale: nous pouvons utiliser trois lignes de code:

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

userManager.RemovePassword(userId);

userManager.AddPassword(userId, newPassword);

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

Maintenant recommandé

Il est probablement préférable d'utiliser la réponse proposée par EdwardBrey , puis DanielWright élaborée plus tard avec un exemple de code.

Shaun Luttin
la source
1
Merci mon Dieu pour cela, j'ai pensé que je devrais créer un nouveau magasin d'utilisateurs jusqu'à ce que je voie ça!
Luke
Existe-t-il un moyen de le faire directement dans SQL? J'adorerais remettre à mon DBA un sproc à appeler en cas de besoin au lieu d'un exécutable.
Mark Richman
@MarkRichman C'est une nouvelle question. Une chose que vous pouvez faire, cependant, est d'inspecter le T-SQL généré qui s'exécute sur SQL Server.
Shaun Luttin le
3
Attention avec ceci activé, chaque fois que AddPassword échoue (c.-à-d. Complexité insuffisante du mot de passe), l'utilisateur se retrouvera sans mot de passe.
Chris
1
Eh bien, l'approche la plus propre sans contourner les règles métier (car lorsque vous accédez directement au hacheur de mot de passe, il n'y a pas de validation de la complexité du mot de passe) est ce que propose Daniel Wright.
Chris
29

Sur votre UserManager, appelez d'abord GeneratePasswordResetTokenAsync . Une fois que l'utilisateur a vérifié son identité (par exemple en recevant le jeton dans un e-mail), transmettez le jeton à ResetPasswordAsync .

Edward Brey
la source
1
Essayer de comprendre pourquoi ResetPasswordAsync nécessite un ID utilisateur et un moyen raisonnable de l'obtenir de l'utilisateur lorsqu'il se présente avec un jeton. GeneratePasswordReset utilise un jeton de plus de 150 caractères ... il semble que ce soit suffisant pour stocker cryptographiquement un identifiant d'utilisateur afin que je n'ai pas à l'implémenter moi-même. :(
pettys
Je suppose qu'il demande l'ID utilisateur afin qu'il puisse entrer le jeton de réinitialisation dans la base de données d'identité par rapport à cet ID utilisateur. S'il ne le faisait pas, comment le framework pourrait-il savoir si le jeton était valide. Vous devriez pouvoir extraire l'ID utilisateur à l'aide de User.Identity.GetUserId () ou similaire.
Ryan Buddicom
1
Exiger l'ID utilisateur est un choix idiot de la part de l'API, le jeton est déjà dans la base de données lorsque ResetPassword (async) est appelé et il devrait suffire de le valider par rapport à l'entrée.
Filip
@Filip, l'avantage de ResetPasswordAsyncprendre un ID utilisateur est que le fournisseur d'identité n'a besoin d'indexer que les ID utilisateur, pas également les jetons. Cela lui permet de mieux évoluer s'il y a de nombreux utilisateurs.
Edward Brey
1
@Edward Brey Eh bien, comment récupérez-vous l'identifiant de l'utilisateur pour l'appel de réinitialisation?
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);
}

Cet extrait de code est extrait du projet AspNetIdentitySample disponible sur github

sclarson
la source
2

Créer une méthode dans 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
la source
2

Meilleur moyen de réinitialiser le mot de passe dans l'utilisation Asp.Net Core Identity pour l'API Web.

Remarque * : Error () et Result () sont créés pour un usage interne. Vous pouvez revenir que vous voulez.

        [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
la source
2
Cela a également fonctionné pour moi avec Fx v 4.5. L'autre solution n'a pas fonctionné. Fondamentalement, c'était aussi beaucoup plus simple. Vous n'avez même pas vraiment besoin d'obtenir l'utilisateur puisque toutes les méthodes accepteront l'identifiant. J'en avais juste besoin pour une réinitialisation temporaire unique dans mon interface d'administration, donc je n'avais pas besoin de toutes les vérifications d'erreur.
Steve Hiner
1

En cas de réinitialisation du mot de passe, il est recommandé de le réinitialiser en envoyant un jeton de réinitialisation de mot de passe à l'adresse e-mail de l'utilisateur enregistré et en demandant à l'utilisateur de fournir un nouveau mot de passe. Si vous avez créé une bibliothèque .NET facilement utilisable sur Identity Framework avec des paramètres de configuration par défaut. Vous pouvez trouver des détails sur le lien du blog et le code source sur github.

Rahul Garg
la source
1

Je pense que le guide Microsoft pour l'identité ASP.NET est un bon début.

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

Remarque:

Si vous n'utilisez pas AccountController et que vous ne souhaitez pas réinitialiser votre mot de passe, utilisez Request.GetOwinContext().GetUserManager<ApplicationUserManager>();. Si vous n'avez pas le même OwinContext, vous devez en créer un nouveau DataProtectorTokenProvidercomme celui OwinContextutilisé. Par défaut, regardez App_Start -> IdentityConfig.cs. Devrait ressembler à quelque chose comme new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));.

Pourrait être créé comme ceci:

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

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

Le DpapiDataProtectionProvideret DataProtectorTokenProviderdoit être créé avec le même nom pour que la réinitialisation du mot de passe fonctionne. Utiliser Owin pour créer le jeton de réinitialisation de mot de passe, puis en créer un nouveau DpapiDataProtectionProvideravec un autre nom ne fonctionnera pas.

Code que j'utilise pour l'identité 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
la source
1

J'ai fait une petite enquête et la solution qui fonctionne pour moi était un mélange de quelques solutions fondées dans cet article.

Je compile essentiellement cette solution et je poste ce qui fonctionne pour moi. Dans mon cas, je ne veux pas utiliser de jeton 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);

}
AFetter
la source