Password History

Mar 1, 2013 at 3:32 PM
It would be handy if there was some form of Password History built into this solution.
The idea being to allow you to limit password reuse.
Mar 4, 2013 at 3:12 PM
Edited Mar 4, 2013 at 3:17 PM
I'm looking at adding it to my copy this way:
Creating a new entity :
 public class UserPasswordHistory
    {       
        [Key]
        [Column(Order = 0)]
        public virtual Guid UserId { get; set; }
        
        [Required, DataType(DataType.Password)]
        public virtual String Password { get; set; }
        
        [Key]
        [Column(Order = 1)]
        public virtual DateTime ChangeDate { get; set; }

        [ForeignKey("UserId")]
        public virtual User user { get; set; }
    }
A small change to the User Entity - adding the password history collection:
    public class User
    {
        [Key]
        public virtual Guid UserId { get; set; }

        [Required]
        public virtual String Username { get; set; }

        public virtual String Email { get; set; }

        [Required, DataType(DataType.Password)]
        public virtual String Password { get; set; }

        public virtual String FirstName { get; set; }
        public virtual String LastName { get; set; }

        [DataType(DataType.MultilineText)]
        public virtual String Comment { get; set; }

        public virtual Boolean IsApproved { get; set; }
        public virtual int PasswordFailuresSinceLastSuccess { get; set; }
        public virtual DateTime? LastPasswordFailureDate { get; set; }
        public virtual DateTime? LastActivityDate { get; set; }
        public virtual DateTime? LastLockoutDate { get; set; }
        public virtual DateTime? LastLoginDate { get; set; }
        public virtual String ConfirmationToken { get; set; }
        public virtual DateTime? CreateDate { get; set; }
        public virtual Boolean IsLockedOut { get; set; }
        public virtual DateTime? LastPasswordChangedDate { get; set; }
        public virtual String PasswordVerificationToken { get; set; }
        public virtual DateTime? PasswordVerificationTokenExpirationDate { get; set; }

        public virtual ICollection<Role> Roles { get; set; }

        public virtual ICollection<UserPasswordHistory> passwordHistory { get; set; }
    }
Then a change to the ChangePassword Method:

public override bool ChangePassword(string username, string currentPassword, string newPassword)
    {
        if (string.IsNullOrEmpty(username))
        {
            return false;
        }
        if (string.IsNullOrEmpty(currentPassword))
        {
            return false;
        }
        if (string.IsNullOrEmpty(newPassword))
        {
            return false;
        }
        
        using (DataContext Context = new DataContext())
        {
            User User = null;
            User = Context.Users.FirstOrDefault(Usr => Usr.Username == username);
            if (User == null)
            {
                return false;
            }
            String HashedPassword = User.Password;
            Boolean VerificationSucceeded = (HashedPassword != null && Crypto.VerifyHashedPassword(HashedPassword, currentPassword));
            if (VerificationSucceeded)
            {
                User.PasswordFailuresSinceLastSuccess = 0;
            }
            else
            {
                int Failures = User.PasswordFailuresSinceLastSuccess;
                if (Failures < MaxInvalidPasswordAttempts)
                {
                    User.PasswordFailuresSinceLastSuccess += 1;
                    User.LastPasswordFailureDate = DateTime.UtcNow;
                }
                else if (Failures >= MaxInvalidPasswordAttempts)
                {
                    User.LastPasswordFailureDate = DateTime.UtcNow;
                    User.LastLockoutDate = DateTime.UtcNow;
                    User.IsLockedOut = true;
                }
                Context.SaveChanges();
                return false;
            }
            String NewHashedPassword = Crypto.HashPassword(newPassword);
            if (NewHashedPassword.Length > 128)
            {
                return false;
            }

            //////////////////////////////////////////
            //Check Password History for reuse
            //////////////////////////////////////////

            int numberOfPasswordsToCheckForReUse = 6;
            var oldPasswords = User.passwordHistory.Take(numberOfPasswordsToCheckForReUse).OrderByDescending(oldPass => oldPass.ChangeDate);
            if (oldPasswords.Where(oldPassword => Crypto.VerifyHashedPassword(oldPassword.Password, newPassword)).Count() == 0)
            {
                UserPasswordHistory oldPassword = new UserPasswordHistory();
                oldPassword.ChangeDate = DateTime.UtcNow;
                oldPassword.UserId = User.UserId;
                oldPassword.Password = User.Password;

                User.Password = NewHashedPassword;
                User.LastPasswordChangedDate = DateTime.UtcNow;
                User.passwordHistory.Add(oldPassword);

            }
            else
            {
                return false;
            }
            Context.SaveChanges();
            return true;
        }
    }
The only problem is there's no way of identifying that the problem is that a password has been reused when ChangePassword returns False, and Membership Provider doesn't provide a method for checking this.