Hasło resetowania ASP.NET Identity

98

Jak mogę uzyskać hasło użytkownika w nowym systemie ASP.NET Identity? Lub jak mogę zresetować, nie znając bieżącego (użytkownik zapomniał hasła)?

daniel
źródło

Odpowiedzi:

104

W aktualnej wersji

Zakładając, że zweryfikowałeś żądanie zresetowania zapomnianego hasła, użyj poniższego kodu jako przykładowych kroków kodu.

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

W AspNet Nightly Build

Struktura została zaktualizowana do współpracy z tokenem do obsługi żądań, takich jak ForgetPassword. Po wydaniu oczekuje się prostych wskazówek dotyczących kodu.

Aktualizacja:

Ta aktualizacja ma na celu zapewnienie bardziej przejrzystych kroków.

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
źródło
czy wiesz, kiedy zostanie wydana wersja 1.1?
graycrow,
Nadal jest w wersji alfa, a wersja 1.0 została właśnie wydana. Więc załóżmy wiele miesięcy. myget.org/gallery/aspnetwebstacknightly
jd4u
11
Co dziwne, wywołanie metody store.SetPasswordHashAsync (cUser, hashedNewPassword) nie działało dla mnie, zamiast tego musiałem ręcznie ustawić cUser.PasswordHash = hashedNewPassword, a następnie wywołać UserManager.UpdateAsync (użytkownik);
Andy Mehalick
1
Kod nie działa jest możliwy tylko wtedy, gdy kontekst pobierania użytkownika i kontekst sklepu są różne. Kod był tylko przykładowymi krokami, nie był dokładny. Wkrótce zaktualizuje odpowiedź, aby uniknąć tego problemu dla innych.
jd4u
1
Framework 1 nie zapewnia. Ale Framework 2-alpha ma kilka funkcji, które mogą zapewnić prosty proces obsługi żądań resetowania hasła. aspnetidentity.codeplex.com
jd4u
141

Lub jak mogę zresetować, nie znając bieżącego (użytkownik zapomniał hasła)?

Jeśli chcesz zmienić hasło za pomocą UserManager, ale nie chcesz podawać aktualnego hasła użytkownika, możesz wygenerować token resetowania hasła, a następnie natychmiast go użyć.

string resetToken = await UserManager.GeneratePasswordResetTokenAsync(model.Id);
IdentityResult passwordChangeResult = await UserManager.ResetPasswordAsync(model.Id, resetToken, model.NewPassword);
Daniel Wright
źródło
8
Jest to zdecydowanie najlepszy i najczystszy sposób ustawienia nowego hasła. Problem z zaakceptowaną odpowiedzią polega na tym, że omija ona weryfikację złożoności hasła, uzyskując bezpośredni dostęp do funkcji mieszającej hasła.
Chris
6
Fyi, może pojawić się błąd „Nie zarejestrowano IUserTokenProvider”. jeśli użyjesz powyższej logiki. Zobacz ten stackoverflow.com/questions/22629936/… .
Prasad Kanaparthi
1
Przypuszczam, że działa to dla Microsoft.AspNet.Identity tylko w wersji 2. Nie można znaleźć metody GeneratePasswordResetTokenAsync w wersji 1.
romanoza
Dziękuję za Twoją odpowiedź. Dla mnie działa jak urok.
Thomas.Benz,
4
Jeśli otrzymasz nieprawidłowy token , upewnij się, że SecurityStampdla użytkownika nie ma wartości NULL. Może się to zdarzyć w przypadku użytkowników migrowanych z innych baz danych lub użytkowników, którzy nie zostali utworzeni za pomocą UserManager.CreateAsync()metody.
Alisson
70

Przestarzałe

To była oryginalna odpowiedź. Działa, ale ma problem. A jeśli AddPasswordzawiedzie? Użytkownik pozostaje bez hasła.

Oryginalna odpowiedź: możemy użyć trzech linii kodu:

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

userManager.RemovePassword(userId);

userManager.AddPassword(userId, newPassword);

Zobacz też: http://msdn.microsoft.com/en-us/library/dn457095(v=vs.111).aspx

Teraz zalecane

Prawdopodobnie lepiej jest użyć odpowiedzi, którą zaproponował EdwardBrey, a następnie DanielWright później opracował przykładowy kod.

Shaun Luttin
źródło
1
Dzięki Bogu za to, myślałem, że będę musiał stworzyć nowy sklep użytkowników, dopóki tego nie zobaczę!
Łukasz
Czy można to zrobić bezpośrednio w SQL? Chciałbym wręczyć mojemu DBA sproc, aby zadzwonił w razie potrzeby, zamiast pliku wykonywalnego.
Mark Richman
@MarkRichman To nowe pytanie. Jedną rzeczą, którą możesz zrobić, jest jednak zbadanie wygenerowanego T-SQL, który działa na SQL Server.
Shaun Luttin,
3
Uważaj z tym włączonym, gdy AddPassword zawiedzie (tj. Niewystarczająca złożoność hasła), użytkownik zostanie bez hasła.
Chris
1
Cóż, najczystszym podejściem bez ominięcia jakichkolwiek reguł biznesowych (ponieważ kiedy uzyskujesz bezpośredni dostęp do haszera haseł, nie ma weryfikacji złożoności hasła), zaproponował Daniel Wright.
Chris
29

Na swoje UserManagerpierwsze zadzwonić GeneratePasswordResetTokenAsync . Gdy użytkownik zweryfikuje swoją tożsamość (na przykład otrzymując token w wiadomości e-mail), przekaż token do ResetPasswordAsync .

Edward Brey
źródło
2
Próbuje dowiedzieć się, dlaczego ResetPasswordAsync wymaga identyfikatora użytkownika i rozsądnego sposobu uzyskania go od użytkownika, gdy pojawiają się z tokenem. GeneratePasswordReset używa tokena, który ma ponad 150 znaków ... wygląda na to, że wystarczyłoby do kryptograficznego przechowywania identyfikatora użytkownika, więc nie muszę tego sam implementować. :(
pettys
Zakładam, że prosi o identyfikator użytkownika, aby mógł wprowadzić token resetowania do bazy danych tożsamości dla tego identyfikatora użytkownika. Gdyby tego nie zrobił, w jaki sposób framework kiedykolwiek wiedziałby, czy token jest ważny. Powinieneś być w stanie pobrać identyfikator użytkownika za pomocą User.Identity.GetUserId () lub podobnego.
Ryan Buddicom
1
Wymaganie identyfikatora użytkownika jest głupim wyborem ze strony interfejsu API, token jest już w bazie danych, gdy wywoływana jest funkcja ResetPassword (async) i powinno wystarczyć tylko do sprawdzenia poprawności danych wejściowych.
Filip
@Filip, zaletą ResetPasswordAsyncpobierania identyfikatora użytkownika jest to, że dostawca tożsamości musi tylko indeksować identyfikatory użytkowników, a nie także tokeny. Pozwala to na lepsze skalowanie, jeśli jest wielu użytkowników.
Edward Brey
2
@Edward Brey No cóż, jak pobrać identyfikator użytkownika dla wywołania resetującego?
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);
}

Ten fragment kodu jest pobierany z projektu AspNetIdentitySample dostępnego na github

sclarson
źródło
2

Utwórz metodę w 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
źródło
2

Najlepszy sposób na resetowanie hasła w użyciu tożsamości Asp.Net Core dla interfejsu API sieci Web.

Uwaga * : Error () i Result () są tworzone do użytku wewnętrznego. Możesz wrócić, jak chcesz.

        [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
źródło
2
Działało to również dla mnie z Fx v 4.5. Drugie rozwiązanie nie zadziałało. Zasadniczo było to też znacznie prostsze. Tak naprawdę nie musisz nawet pozyskiwać użytkownika, ponieważ wszystkie metody akceptują identyfikator. Potrzebowałem go tylko do tymczasowego jednorazowego resetowania w interfejsie administratora, więc nie potrzebowałem wszystkich sprawdzeń błędów.
Steve Hiner
1

W przypadku resetowania hasła zaleca się zresetowanie go poprzez wysłanie tokena resetowania hasła na adres e-mail zarejestrowanego użytkownika i poproszenie użytkownika o podanie nowego hasła. Jeśli utworzyłeś łatwą w użyciu bibliotekę .NET na platformie Identity z domyślnymi ustawieniami konfiguracji. Szczegóły można znaleźć pod linkiem do bloga i kodem źródłowym na github.

Rahul Garg
źródło
1

Myślę, że przewodnik firmy Microsoft dotyczący ASP.NET Identity to dobry początek.

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

Uwaga:

Jeśli nie używasz narzędzia AccountController i nie chcesz zresetować hasła, użyj Request.GetOwinContext().GetUserManager<ApplicationUserManager>();. Jeśli nie masz tego samego OwinContext, musisz utworzyć nowy, DataProtectorTokenProvidertaki jak ten, którego OwinContextużywa. Domyślnie spójrz na App_Start -> IdentityConfig.cs. Powinien wyglądać jak new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));.

Można stworzyć w ten sposób:

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

Z Owinem:

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

Aby resetowanie hasła działało, należy utworzyć DpapiDataProtectionProvideri DataProtectorTokenProvidermusi mieć tę samą nazwę. Używanie Owin do tworzenia tokena resetowania hasła, a następnie tworzenie nowego DpapiDataProtectionProvidero innej nazwie nie zadziała.

Kod, którego używam dla 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
źródło
1

Zrobiłem małe dochodzenie i rozwiązaniem, które działa dla mnie, było połączenie kilku rozwiązań przedstawionych w tym poście.

Zasadniczo kompiluję to rozwiązanie i publikuję, co działa dla mnie. W moim przypadku nie chcę używać żadnego tokena z .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
źródło
„To, co działa dla mnie”, nie jest po prostu wystarczające do czegoś związanego z bezpieczeństwem. Chcę używać jak największej liczby gotowych programów .NET Core.
Heinzlmaen