ASP.NET Core 2.2 - Password Requirements


Ken Haggerty
Created 03/12/2019 - Updated 03/14/2019 07:29

This article will cover password requirements and settings for ASP.NET Core Identity. I will assume you have created a new ASP.NET Core 2.2 Razor Pages project with Individual User Accounts, updated the database with the CreateIdentitySchema migration and scaffolded the Identity UI.

Let's start by listing the requirements with their default values.

Edit Startup.cs > ConfigureServices > AddIdentity, add Password requirements:
services.AddIdentity<IdentityUser, IdentityRole>(config =>
    {
        config.SignIn.RequireConfirmedEmail = true;
        config.User.RequireUniqueEmail = true;
        // Password requirements
        config.Password.RequireDigit = true;
        config.Password.RequiredLength = 6;
        config.Password.RequiredUniqueChars = 1;
        config.Password.RequireLowercase = true;
        config.Password.RequireNonAlphanumeric = true;
        config.Password.RequireUppercase = true;
    })
    .AddEntityFrameworkStores<ApplicationDbContext>();
    .AddDefaultTokenProviders();

Most of these settings need no more explanation. RequiredUniqueChars = 1 is ineffective when the other requirements are applied. I have wondered exactly which characters qualify as non-alphanumeric. My research found nonWordCharacters: "./\\()\"':,.;<>!@#$%^&*|+=[]{}`~?-".

Section references:

I feel Password.RequiredLength = 6 is too weak. I set my required length to 8. If you change the required length, you should update the StringLength attribute on the InputModel password property in Register.cshtml.cs, ChangePassword.cshtml.cs, ResetPassword.cshtml.cs and SetPassword.cshtml.cs.

Edit StringLength > ErrorMessage > MinimumLength:
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 8)]

When a new user registers, they must fail at least once to get feedback on the requirements.

Invalid Register Before

You can add a list of the requirements where the user creates or updates their password. (Register.cshtml, ChangePassword.cshtml, ResetPassword.cshtml and SetPassword.cshtml)

Edit (Register.cshtml, ChangePassword.cshtml, ResetPassword.cshtml and SetPassword.cshtml) > Password Input form-group, add list of password requirements:
<div class="form-group">
    <label asp-for="Input.Password"></label>
    <div id="PasswordUpperDivId" class="invalid password_requirement">At least one upper case letter.</div>
    <div id="PasswordLowerDivId" class="invalid password_requirement">At least one lower case letter.</div>
    <div id="PasswordDigitDivId" class="invalid password_requirement">At least one digit.</div>
    <div id="PasswordSpecialDivId" class="invalid password_requirement">At least one special character.</div>
    <div id="PasswordLengthDivId" class="invalid password_requirement mb-2">At least 8 characters long.</div>
    <input asp-for="Input.Password" class="form-control" />
    <span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
Edit site.css, add invalid and valid password requirement classes:
.invalid.password_requirement {
    color: orange;
}

.valid.password_requirement {
    color: green;
}

You can use a little JavaScript to update the class when the user satisfies a requirement. Notice I exclude the login and deletepersonaldata paths which have password inputs but not the list.

Edit site.js, add password requirement script:
var currentPath = window.location.pathname;
if (currentPath !== '/identity/account/login' && currentPath !== '/identity/account/manage/deletepersonaldata'
    &&(document.querySelector('#Input_Password') || document.querySelector('#Input_NewPassword'))) {

    var passwordInput = document.querySelector('#Input_Password');
    if (!passwordInput) {
            passwordInput = document.querySelector('#Input_NewPassword');
    }

    var upper = document.querySelector('#PasswordUpperDivId');
    var lower = document.querySelector('#PasswordLowerDivId');
    var digit = document.querySelector('#PasswordDigitDivId');
    var special = document.querySelector('#PasswordSpecialDivId');
    var length = document.querySelector('#PasswordLengthDivId');
        
    // When the user starts to type something inside the password field
    passwordInput.addEventListener('keyup', function () {

        // Validate capital letters
        var upperCaseLetters = /[A-Z]/g;
        if (passwordInput.value.match(upperCaseLetters)) {
            upper.classList.remove('invalid');
            upper.classList.add('valid');
        } else {
            upper.classList.remove('valid');
            upper.classList.add('invalid');
        }

        // Validate lowercase letters
        var lowerCaseLetters = /[a-z]/g;
        if (passwordInput.value.match(lowerCaseLetters)) {
            lower.classList.remove('invalid');
            lower.classList.add('valid');
        } else {
            lower.classList.remove('valid');
            lower.classList.add('invalid');
        }

        // Validate digit
        var numbers = /[0-9]/g;
        if (passwordInput.value.match(numbers)) {
            digit.classList.remove('invalid');
            digit.classList.add('valid');
        } else {
            digit.classList.remove('valid');
            digit.classList.add('invalid');
        }

        // Validate special
        var specials = /\W/g;
        if (passwordInput.value.match(specials)) {
            special.classList.remove('invalid');
            special.classList.add('valid');
        } else {
            special.classList.remove('valid');
            special.classList.add('invalid');
        }

        // Validate length
        if (passwordInput.value.length >= 8) {
            length.classList.remove('invalid');
            length.classList.add('valid');
        } else {
            length.classList.remove('valid');
            length.classList.add('invalid');
        }
    });
}

The user can still submit the form without meeting the requirements. You can add a simple model validation with the RegularExpression attribute on the InputModel password property in Register.cshtml.cs, ChangePassword.cshtml.cs, ResetPassword.cshtml.cs and SetPassword.cshtml.cs. The RegEx = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W]).{8,}$" includes a match for minimum length of 8 which makes the StringLength attribute redundant.

Edit InputModel > Password property, add RegularExpressionAttribute:
[Required]
//[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 8)]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W]).{8,}$", ErrorMessage = "The {0} does not meet requirements.")]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }

Now when a new user registers, they are aware of the password requirements.

Invalid Register After


2 Following


Comment Count = 0

Please log in to comment or follow.

Login Register