Password di reimpostazione identità ASP.NET


95

Come posso ottenere la password di un utente nel nuovo sistema di identità ASP.NET? O come posso resettare senza conoscere quella attuale (password dimenticata dall'utente)?

Risposte:


102

Nella versione attuale

Supponendo che tu abbia gestito la verifica della richiesta di reimpostazione della password dimenticata, utilizza il codice seguente come passaggi del codice di esempio.

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

Il framework viene aggiornato per funzionare con Token per la gestione di richieste come ForgetPassword. Una volta rilasciato, è prevista una semplice guida al codice.

Aggiornare:

Questo aggiornamento serve solo per fornire passaggi più chiari.

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

sai quando verrà rilasciata la versione 1.1?
graycrow

È ancora in alpha e 1.0 è appena stato rilasciato. Quindi presumi molti mesi. myget.org/gallery/aspnetwebstacknightly
jd4u

11
Stranamente la chiamata al metodo store.SetPasswordHashAsync (cUser, hashedNewPassword) non ha funzionato per me, invece ho dovuto impostare manualmente cUser.PasswordHash = hashedNewPassword e quindi chiamare UserManager.UpdateAsync (utente);
Andy Mehalick

1
Il codice non funziona è possibile solo se il contesto di recupero dell'utente e il contesto del negozio sono diversi. Il codice era solo un esempio di passaggi, non accurato. Aggiornerò presto la risposta per evitare questo problema ad altri.
jd4u

1
Framework 1 non fornisce. Ma Framework 2-alpha ha poche funzionalità in grado di fornire un processo semplice per la gestione delle richieste di reimpostazione della password. aspnetidentity.codeplex.com
jd4u

138

O come posso resettare senza conoscere quella attuale (password dimenticata dall'utente)?

Se si desidera modificare una password utilizzando UserManager ma non si desidera fornire la password corrente dell'utente, è possibile generare un token di reimpostazione della password e quindi utilizzarlo immediatamente.

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

8
Questo è di gran lunga il modo migliore e più pulito per impostare una nuova password. Il problema con la risposta accettata è che ignora le convalide della complessità della password accedendo direttamente all'hash della password.
Chris

6
Cordiali saluti, potresti ricevere l'errore "Nessun IUserTokenProvider è registrato." se usi la logica sopra. Vedi questo stackoverflow.com/questions/22629936/… .
Prasad Kanaparthi

1
Suppongo che funzioni solo per Microsoft.AspNet.Identity nella versione 2. Non riesci a trovare il metodo GeneratePasswordResetTokenAsync nella versione 1.
romanoza

La ringrazio per la risposta. Funziona come un fascino per me.
Thomas.Benz

4
Se ottieni un token non valido , assicurati che SecurityStampper il tuo utente non sia nullo. Ciò può accadere per gli utenti migrati da altri database o per gli utenti che non sono stati creati tramite il UserManager.CreateAsync()metodo.
Alisson

70

Deprecato

Questa era la risposta originale. Funziona, ma ha un problema. E se AddPasswordfallisse? L'utente viene lasciato senza password.

La risposta originale: possiamo usare tre righe di codice:

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

userManager.RemovePassword(userId);

userManager.AddPassword(userId, newPassword);

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

Ora consigliato

Probabilmente è meglio usare la risposta proposta da EdwardBrey e poi elaborata da DanielWright con un esempio di codice.


1
Grazie a Dio per questo, ho pensato di dover creare un nuovo negozio utente fino a quando non l'ho visto!
Luca

C'è un modo per farlo direttamente in SQL? Mi piacerebbe dare al mio DBA uno sproc da chiamare quando necessario invece di un eseguibile.
Mark Richman

@MarkRichman Questa è una nuova domanda. Una cosa che potresti fare, tuttavia, è ispezionare il T-SQL generato che viene eseguito su SQL Server.
Shaun Luttin

3
Attenzione con questo attivo, ogni volta che AddPassword fallisce (cioè complessità della password insufficiente), l'utente rimarrà senza password.
Chris

1
Ebbene, l'approccio più pulito senza aggirare le regole aziendali (perché quando si accede direttamente all'hash della password non c'è alcuna convalida della complessità della password) è quello che ha proposto Daniel Wright.
Chris

29

Alla tua UserManagerprima chiamata GeneratePasswordResetTokenAsync . Una volta che l'utente ha verificato la sua identità (ad esempio ricevendo il token in un'e-mail), passa il token a ResetPasswordAsync .


1
Cercando di capire perché ResetPasswordAsync richiede un ID utente e un modo ragionevole per ottenerlo dall'utente quando si presenta con un token. GeneratePasswordReset utilizza un token di oltre 150 caratteri ... sembra che sarebbe sufficiente per archiviare crittograficamente un ID utente, quindi non devo implementarlo da solo. :(
pettys

Presumo che stia chiedendo l'ID utente in modo che possa inserire il token di ripristino nel database di identità rispetto a quell'ID utente. Se non lo facesse, come potrebbe mai il framework sapere se il token è valido. Dovresti essere in grado di estrarre l'ID utente utilizzando User.Identity.GetUserId () o simili.
Ryan Buddicom

1
Richiedere l'ID utente è una scelta stupida da parte dell'API, il token è già nel database quando viene chiamato ResetPassword (async) e dovrebbe essere sufficiente solo per convalidarlo rispetto all'input.
Filip

@Filip, il vantaggio di ResetPasswordAsyncprendere un ID utente è che il provider di identità deve solo indicizzare gli ID utente, non anche i token. Ciò consente di ridimensionare meglio se ci sono molti utenti.
Edward Brey

1
@Edward Brey, beh, come prendi l'ID utente per la chiamata di ripristino?
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);
}

Questo frammento di codice viene estratto dal progetto AspNetIdentitySample disponibile su GitHub


2

Crea metodo 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);
}

1

In caso di reimpostazione della password, si consiglia di reimpostarla inviando il token di reimpostazione della password all'e-mail dell'utente registrato e chiedere all'utente di fornire una nuova password. Se hai creato una libreria .NET facilmente utilizzabile su Identity framework con impostazioni di configurazione predefinite. Puoi trovare i dettagli sul link del blog e il codice sorgente su GitHub.


1

Penso che la guida Microsoft per ASP.NET Identity sia un buon inizio.

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

Nota:

Se non utilizzi AccountController e non desideri reimpostare la password, utilizza Request.GetOwinContext().GetUserManager<ApplicationUserManager>();. Se non hai lo stesso OwinContext devi crearne uno nuovo DataProtectorTokenProvidercome quello che OwinContextusa. Per impostazione predefinita guarda App_Start -> IdentityConfig.cs. Dovrebbe assomigliare a qualcosa new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));.

Potrebbe essere creato in questo modo:

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

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

Il DpapiDataProtectionProvidere DataProtectorTokenProviderdeve essere creato con lo stesso nome affinché la reimpostazione della password funzioni. L'utilizzo di Owin per creare il token di reimpostazione della password e quindi crearne uno nuovo DpapiDataProtectionProvidercon un altro nome non funzionerà.

Codice che utilizzo per 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; }
}

1

Ho fatto una piccola indagine e la soluzione che funziona per me è stata un mix di poche soluzioni fondate in questo post.

Fondamentalmente sto compilando questa soluzione e sto pubblicando ciò che funziona per me. Nel mio caso, non voglio utilizzare alcun token da .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);

}

1

Il modo migliore per reimpostare la password nell'uso di Asp.Net Core Identity per l'API Web.

Nota * : Error () e Result () vengono creati per uso interno. Puoi tornare che vuoi.

        [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
Questo ha funzionato anche per me con Fx v 4.5. L'altra soluzione non ha funzionato. Fondamentalmente anche questo era molto più semplice. Non hai nemmeno davvero bisogno di ottenere l'utente poiché tutti i metodi accetteranno l'id. Ne avevo solo bisogno per un ripristino temporaneo una tantum nella mia interfaccia di amministrazione, quindi non avevo bisogno di tutti i controlli degli errori.
Steve Hiner
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.