Implementing custom password policy using ASP.NET Identity

Implementing custom password policy using ASP.NET Identity

Rate This
  • Comments 10

We recently released the 2.0.0-alpha1 version of ASP.NET Identity. Learn more here: http://blogs.msdn.com/b/webdev/archive/2013/12/20/announcing-preview-of-microsoft-aspnet-identity-2-0-0-alpha1.aspx

To provide a more secure user experience for the application, you might want to customize the password complexity policy. This might include minimum password length, mandatory special characters in the password, disallowing recently used passwords, etc. More information on password policy can be found here. By default ASP.NET Identity enforces a minimum password length of 6 characters. This article provides a simple walkthrough that shows how to add a few more password policies to an application.

We will create an application using Visual Studio 2013, update the Identity assemblies to 2.0.0-alpha1, and then add code to enforce the following password policies:

i. Change the default password length requirement from 6 characters to 10 characters.

ii. The password should have at least one number and one special character.

iii. The user cannot reuse any of the last 5 previous passwords.

Lastly we’ll run the application to verify if these policies have been successfully enforced.

Create application

  • In Visual Studio 2013 RTM create a new 4.5 ASP.NET Web application. Choose MVC.

image

  • Right-click the project and select ‘Manage Nuget Packages’. Go to the ‘Update’ section and update the Identity packages to 2.0.0-alpha1

image

  • Let’s quickly look at the user creation flow in the application. This will help us outline the password validation steps in the Identity system. Under the Controllers folder, the AccountController has actions for user account management.
    • The Account controller uses an instance of UserManager class which is defined in the Identity framework. This takes a UserStore class which has persistence-specific API for user management. In our case it uses Entity Framework.
    • The Register post method creates a new user. The call to CreateAsync in turn calls the ‘ValidateAsync’ method on the PasswordValidator property to validate the supplied password.
 1: var user = new ApplicationUser() { UserName = model.UserName };
 2:  
 3: var result = await UserManager.CreateAsync(user, model.Password);
 4:  
 5: if (result.Succeeded)
 6: {
 7: await SignInAsync(user, isPersistent: false);
 8:  
 9: return RedirectToAction("Index", "Home");
 10: }
 11:  
 
      
    • On success the user is signed in.

Similarly, password validation is called when the user tries to change the password. This can be observed in the ‘Manage’ post action.

Policy 1: Change the default password length requirement from 6 characters to 10 characters.

By default the PasswordValidator property in UserManager is set to the MinimumLengthValidator class which validates that the password length is at least 6. This value can be changed when instantiating a UserManager object.

  • The minimum password length value can be changed in the AccountController in the constructor where the UserManager is instantiated or at a global level by defining a separate class derived from UserManager. The second approach is possible by extending the UserManager class in the application and setting the property. Under the IdentityExtensions folder, create a new class ApplicationUserManager which extends from UserManager<ApplicationUser>.

    

 1: public class ApplicationUserManager : UserManager<ApplicationUser>
 2: {
 3:         public ApplicationUserManager(): base(new UserStore<ApplicationUser>(new ApplicationDbContext()))
 4:         {
 5:               PasswordValidator = new MinimumLengthValidator (10);
 6:         }
 7: }
 8: 

The UserManager class takes an IUserStore class which is a persistence-specific implementation of user management APIs. Since the UserStore class is EntityFramework-specific, we need to pass in the DbContext object. This call is essentially the same as defined in the constructor of the AccountController class.

The additional advantage of defining the custom ApplicationUserManager class is that we can override methods defined in the UserManager class, which we will be doing for the subsequent password policies.

  • We then need to hook in the ApplicationUserManager class defined in the above step in the AccountController. Change the constructor and the UserManager Property in the AccountController as below.
      
 1: public AccountController() : this(new ApplicationUserManager())
 2:         {
 3:         }
 4:  
 5:         public AccountController(ApplicationUserManager userManager)
 6:         {
 7:             UserManager = userManager;
 8:         }
 9:         public ApplicationUserManager UserManager { get; private set; }
 

This way we are making the AccountController use the custom UserManager. This enables you to do additional customization while keeping the other code in the controller intact.

  • Run the application and try to register a local user. If the password is less than 10 characters in length, you’ll get the following error message.

image

Policy 2: Passwords should have at least one special character and one numeral

As mentioned in the previous section, the PasswordValidator property in the UserManager class validates the length of the password to 6 characters. Though we can change the ‘RequiredLength’, to add additional checks we would need to extend the behavior through a new class. We can add new password validators by creating a class that implements the IIdentityValidator interface and adding the validations as needed. In our case we want to check for special characters and numbers in the supplied password along with checking if it is of minimum length in the ‘ValidateAsync’ method.

  • Create a new folder under the root project called IdentityExtensions. Add a new class CustomPasswordValidator implementing IIdentityValidator<string> since password is of type string.
 1: public class CustomPasswordValidator : IIdentityValidator<string>
 2:  
 3: {
 4:  
 5: public int RequiredLength { get; set; }
 6:  
 7: public CustomPasswordValidator(int length)
 8:  
 9: {
 10:  
 11: RequiredLength = length;
 12:  
 13: }
 14:  
 15: public Task<IdentityResult> ValidateAsync(string item)
 16:  
 17: {
 18:  
 19: if (String.IsNullOrEmpty(item) || item.Length < RequiredLength)
 20:  
 21: {
 22:  
 23: return Task.FromResult(IdentityResult.Failed(String.Format("Password should be of length {0}",RequiredLength)));
 24:  
 25: }
 26:  
 27: string pattern = @"^(?=.*[0-9])(?=.*[!@#$%^&*])[0-9a-zA-Z!@#$%^&*0-9]{10,}$";
 28:  
 29: if (!Regex.IsMatch(item, pattern))
 30:  
 31: {
 32:  
 33: return Task.FromResult(IdentityResult.Failed("Password should have one numeral and one special character"));
 34:  
 35: }
 36:  
 37: return Task.FromResult(IdentityResult.Success);
 38:  
 39: }

In the ValidateAsync method, we check if the password is of the specified length. Then we use a regular expression to verify if the password has a special character and a numeric character. Note: The regular expression is not tested fully and should be used as a sample only.

  • Next we need to set this validator in the PasswordValidator property of the UserManager. Navigate to the ApplicationUserManager created earlier and change the PasswordValidator property from MinimumLengthValidator to CustomPasswordValidator.
 1: public class ApplicationUserManager : UserManager<ApplicationUser[PR6] >
 2:  
 3: {
 4:  
 5: public ApplicationUserManager()
 6:  
 7: : base(new UserStore<ApplicationUser(new ApplicationDbContext()))
 8:  
 9: {
 10:  
 11: PasswordValidator = new CustomPasswordValidator(10);
 12:  
 13: }
 14:  
 15: }
  • No change is needed in the AccountController class. Now try to create a user with password that does not meet the requirements. An error message is displayed as shown below.

image

Policy 3: The user cannot reuse the last five previous passwords

When existing users change or reset their password, we can check if they are trying to reuse an old password. This is not done by default since the Identity system does not store the password history per user. We can do this in the application by creating a separate table to store password history per user and then retrieve the password hashes whenever the user tries to reset or change password.

  • Open the IdentityModels.cs file in the editor. Create a new class called ‘PreviousPassword’ as shown below.
 1: public class PreviousPassword
 2:  
 3: {
 4:  
 5: public PreviousPassword()
 6:  
 7: {
 8:  
 9: CreateDate = DateTimeOffset.Now;
 10:  
 11: }
 12:  
 13: [Key, Column(Order = 0)]
 14:  
 15: public string PasswordHash { get; set; }
 16:  
 17: public DateTimeOffset CreateDate { get; set; }
 18:  
 19: [Key, Column(Order = 1)]
 20:  
 21: public string UserId { get; set; }
 22:  
 23: public virtual ApplicationUser User { get; set; }
 24:  
 25: }

In this class, the ‘Password’ field holds the hashed password for the user referenced by the field ‘UserId’. The ‘CreateDate’ holds the timestamp when the password was added. This can be used to filter the password history to get the last 5 passwords or the latest password.

EntityFramework code first creates a corresponding table called ’PreviousPasswords’ with the ‘UserId’ and the ‘Password’ field forming the composite primary key. You can read more about code first conventions here.

  • Add a property on the user class ‘ApplicationUser’ to hold a list of previous passwords
 1: public class ApplicationUser : IdentityUser
 2:     {
 3:         public ApplicationUser()
 4:             : base()
 5:         {
 6:             PreviousUserPasswords = new List<PreviousPassword>();
 7:         }
 8:         public virtual IList<PreviousPassword> PreviousUserPasswords { get; set; }
 9:  
 10:     }
  • As explained in Policy 1, the UserStore class holds the persistence-specific methods for user operations. We need to store the password hash in the history table when the user is created for the first time. Since the UserStore does not define such a method separately, we need to define an override for the existing method to store the password history. This can be done by extending the existing UserStore class and override the existing methods to store the passwords. This is then hooked in the ApplicationUserManager. Add a new class under the IdentityExtensions folder.
 1: public class ApplicationUserStore : UserStore<ApplicationUser>
 2:  
 3: {
 4:  
 5: public ApplicationUserStore(DbContext context)
 6:  
 7: : base(context)
 8:  
 9: {
 10:  
 11: }
 12:  
 13: public override async Task CreateAsync(ApplicationUser user)
 14:  
 15: {
 16:  
 17: await base.CreateAsync(user);
 18:  
 19: await AddToPreviousPasswordsAsync(user, user.PasswordHash);
 20:  
 21: }
 22:  
 23: public Task AddToPreviousPasswordsAsync(ApplicationUser user, string password)
 24:  
 25: {
 26:  
 27: user.PreviousUserPasswords.Add(new PreviousPassword() { UserId = user.Id, PasswordHash = password });
 28:  
 29: return UpdateAsync(user);
 30:  
 31: }
 32:  
 33: }

The ‘AddToPreviousPasswordsAsync’ method stores the password in the ‘PreviousPasswords’ table separately

  • We need to call the above method whenever user tries to change or reset a password. The API’s to do this are defined in the UserManager class. We need to override them and include the above password history method call. We can do this by overriding the ChangePassword and ResetPassword methods in the ApplicationUserManager class defined in the Policy 2.
 1: public class ApplicationUserManager : UserManager<ApplicationUser>
 2:  
 3: {
 4:  
 5: private const int PASSWORD_HISTORY_LIMIT = 5;
 6:  
 7: public ApplicationUserManager()
 8:  
 9: : base(new ApplicationUserStore(new ApplicationDbContext()))
 10:  
 11:  {
 12:  
 13: PasswordValidator = new CustomPasswordValidator(10);
 14:  
 15:  }
 16:  
 17: public override async Task<IdentityResult> ChangePasswordAsync(string userId, string currentPassword, string newPassword)
 18:  
 19: {
 20:  
 21: if (await IsPreviousPassword(userId, newPassword))
 22:  
 23: {
 24:  
 25: return await Task.FromResult(IdentityResult.Failed("Cannot reuse old password"));
 26:  
 27: }
 28:  
 29: var result = await base.ChangePasswordAsync(userId, currentPassword, newPassword);
 30:  
 31: if (result.Succeeded)
 32:  
 33: {
 34:  
 35: var store = Store as ApplicationUserStore;
 36:  
 37: await store.AddToPreviousPasswordsAsync(await FindByIdAsync(userId), PasswordHasher.HashPassword(newPassword));
 38:  
 39: }
 40:  
 41: return result;
 42:  
 43: }
 44:  
 45: public override async Task<IdentityResult> ResetPasswordAsync(string userId, string token, string newPassword)
 46:  
 47: {
 48:  
 49: if (await IsPreviousPassword(userId, newPassword))
 50:  
 51: {
 52:  
 53: return await Task.FromResult(IdentityResult.Failed("Cannot reuse old password"));
 54:  
 55: }
 56:  
 57: var result = await base.ResetPasswordAsync(userId, token, newPassword);
 58:  
 59: if (result.Succeeded)
 60:  
 61: {
 62:  
 63: var store = Store as ApplicationUserStore;
 64:  
 65: await store.AddToPreviousPasswordsAsync(await FindByIdAsync(userId), PasswordHasher.HashPassword(newPassword));
 66:  
 67: }
 68:  
 69: return result;
 70:  
 71: }
 72:  
 73: private async Task<bool> IsPreviousPassword(string userId, string newPassword)
 74:  
 75: {
 76:  
 77: var user = await FindByIdAsync(userId);
 78:  
 79: if (user.PreviousUserPasswords.OrderByDescending(x => x.CreateDate).
 80:  
 81: Select(x => x.PasswordHash).Take(PASSWORD_HISTORY_LIMIT).Where(x => PasswordHasher.VerifyHashedPassword(x, newPassword) != PasswordVerificationResult.Failed
 82:  
 83: ).Any())
 84:  
 85: {
 86:  
 87: return true;
 88:  
 89: }
 90:  
 91: return false;
 92:  
 93: }
 94:  
 95: }

The ‘PASSWORD_HISTORY_LIMIT’ field is used to get the last ‘X’ number of passwords from the table. Notice we hooked in the ApplicationUserStore in the constructor to get the new method to store the password. Whenever the user changes the password, we validate it against the last 5 passwords stored in the table and return true/false based on the validation.

  • Create a local user and go to Manage page. Try to change the password using same password when the user is created. You see a message as shown below.

image

Code Sample

The sample project Identity-PasswordPolicy is checked into http://aspnet.codeplex.com/SourceControl/latest under Samples/Identity.

Summary

This post shows how you can extend the ASP.NET Identity Validation system to enforce different types of password policies for the users of your application. The example uses the MVC template, but the same changes can be applied to a web forms application.

I hope you have found this walkthrough useful. If you have any questions around ASP.NET Identity or find issues, please feel free to open bugs on the Identity Codeplex site, https://aspnetidentity.codeplex.com/ , leave comments on this blog, or ask questions on asp.net/forums or StackOverflow(tag: aspnet-identity). I can also be reached on twitter (@suhasbjoshi).


  • Why is Password not of type SecureString?

  • @Tom: Do you mean the 'PasswordHash' property in the new class PreviousPassword ? We store the hash of the password and work with that. It is same as the PasswordHash property in the IdentityUser class

  • @suhasbj: No, I mean the user password, for example passed to UserManager.CreateAsync. Shouldn't security in depth mean to use SecureString for passwords in the .NET framework all over the place (at least as much as possible)?

  • @Tom: Looks like we need to look at it in detail. I have opened a codeplex bug for the team to triage it.

    aspnetidentity.codeplex.com/.../1957

  • Hi, will all this global ApplicationUserManager stuff be in the ASP.NET Identity 2.0 RTM Visual Studio templates?

  • @Woot: We will update the Visual Studio templates in the next update that happens to VS

  • Is there an ETA for that? Sorry I dont know where to find the roadmap. I thought the templates would be updated once ASP.NET Identity 2.0 RTM came out? I think this was in spring 2014. Next VS updates comes out later, yes? Is there or will there be a nuget package for the VS templates so we dont have to wait for VS update? Thanks.

  • I also found a good tutorial here <a href="www.dotnetalchemy.com/.../aspnet_usrmgmt_chngPassPage.aspx" target="_blank">HERE</a>

  • Somehow the link got messed up, here is another tutorial on this subject: www.dotnetalchemy.com/.../aspnet_usrmgmt_chngPassPage.aspx

  • ... and how do you do it without MVC?

Page 1 of 1 (10 items)