Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET - Password Strength Indicator using jQuery and XML

0.00/5 (No votes)
31 Jul 2012 2  
ASP.NET Password Strength Indicator somewhat similar to AJAX PasswordStrength extender control behavior and implemented by using jQuery and XML.

Table of Contents

Introduction

Last week, I had the opportunity to help implement and integrate a strong password policy to the legacy web application developed using ASP technology. The solution I proposed was to use jQuery to display the password strength meter to help users create strong password. Then one of my colleagues had asked, "Do we have to modify the client-side script, code behind and password policy page if the client decided to alter the password policy?" The answer is "no", thanks to jQuery, the client-side script and code behind can share the same XML file. The password policy information is stored to an XML file and the client-side script and code behind are using the information in the XML file to perform the password strength validation.

I found several fine jQuery plug-in to display the user's password strength but I prefer to have something somewhat similar to the ASP.NET AJAX PasswordStrength control. After spending some time doing research, I was able to find all the necessary resources and assemble the jQuery plug-in to achieve the goal. Listed below are the features of the plug-in and I have put together a demo, everyone is welcome to download it.

  1. Display password strength indicator in text and progress bar meter.
  2. The password policy, bar color and width are stored in the XML file and consumed by the client-side and server side
  3. Use XSLT to transform the password policy XML file into HTML.
Figure 1

Password Strength steps

Figure 2

Password policy

Getting Started

Shown below is the content of the sample application. If you want to test the Classic ASP code, deploy the application to your IIS Web Server. The local ASP.NET Development Server does not support Classic ASP.

Figure 3

Project Structure

  • Default.aspx – Sample code in ASP.NET C# with MasterPage
  • Default.aspx2 – Sample code in ASP.NET C# without MasterPage
  • Password_strength_Indicator.asp – Sample code in Classic ASP
  • jQuery_Password_Strength_Indicator.htm – Sample code in HTML
  • PasswordPolicy.xml – Contains password policy information
  • Password.xslt – To transform the PasswordPoclicy.xml file into HTML format
  • jQuery.password-strength.js –This is the plug-in I have created
  • Download latest jQuery library from here

The Password Strength Indicator Plug-in

In this section, I'll touch base briefly with the contents in the jQuery.password-strength.js file. The code is very straight forward and comments are included. There is a jQuery AJAX request to read the XML file and populate the local variables based on the data in the XML file. If you are unsure of the relative URL, I would recommend using absolute URL to your XML file. The getStrengthInfo() function contains the logic to check the password strength and return appropriate message based on user input. The password strength meter bar and text position are relative to the Textbox position. Shown below is the code in the jQuery.password-strength.js file.

Listing 1
(function($) {
    var password_Strength = new function() {

        //return count that match the regular expression
        this.countRegExp = function(passwordVal, regx) {
            var match = passwordVal.match(regx);
            return match ? match.length : 0;
        }

        this.getStrengthInfo = function(passwordVal) {
            var len = passwordVal.length;
            var pStrength = 0; //password strength
            var msg = "", inValidChars = ""; //message

            //get special characters from xml file
            var allowableSpecilaChars = new RegExp
		("[" + password_settings.specialChars + "]", "g")

            var nums = this.countRegExp(passwordVal, /\d/g), //numbers
		lowers = this.countRegExp(passwordVal, /[a-z]/g),
		uppers = this.countRegExp(passwordVal, /[A-Z]/g), //upper case
		specials = this.countRegExp(passwordVal,
				allowableSpecilaChars), //special characters
		spaces = this.countRegExp(passwordVal, /\s/g);

            //check for invalid characters
            inValidChars = passwordVal.replace(/[a-z]/gi, "") +
				inValidChars.replace(/\d/g, "");
            inValidChars = inValidChars.replace(/\d/g, "");
            inValidChars = inValidChars.replace(allowableSpecilaChars, "");

            //check space
            if (spaces > 0) {
                return "No spaces!";
            }

            //invalid characters
            if (inValidChars !== '') {
                return "Invalid character: " + inValidChars;
            }

            //max length
            if (len > password_settings.maxLength) {
                return "Password too long!";
            }

            //GET NUMBER OF CHARACTERS left
            if ((specials + uppers + nums + lowers) < password_settings.minLength) {
                msg += password_settings.minLength -
		(specials + uppers + nums + lowers) + " more characters, ";
            }

            //at the "at least" at the front
            if (specials == 0 || uppers == 0 || nums == 0 || lowers == 0) {
                msg += "At least ";
            }

            //GET NUMBERS
            if (nums >= password_settings.numberLength) {
                nums = password_settings.numberLength;
            }
            else {
                msg += (password_settings.numberLength - nums) + " more numbers, ";
            }

            //special characters
            if (specials >= password_settings.specialLength) {
                specials = password_settings.specialLength
            }
            else {
                msg += (password_settings.specialLength - specials) + " more symbol, ";
            }

            //upper case letter
            if (uppers >= password_settings.upperLength) {
                uppers = password_settings.upperLength
            }
            else {
                msg += (password_settings.upperLength - uppers) +
					" Upper case characters, ";
            }

            //strength for length
            if ((len - (uppers + specials + nums)) >=
		(password_settings.minLength - password_settings.numberLength -
		password_settings.specialLength - password_settings.upperLength)) {
                pStrength += (password_settings.minLength -
		password_settings.numberLength - password_settings.specialLength -
		password_settings.upperLength);
            }
            else {
                pStrength += (len - (uppers + specials + nums));
            }

            //password strength
            pStrength += uppers + specials + nums;

            //detect missing lower case character
            if (lowers === 0) {
                if (pStrength > 1) {
                    pStrength -= 1; //Reduce 1
                }
                msg += "1 lower case character, ";
            }

            //strong password
            if (pStrength == password_settings.minLength && lowers > 0) {
                msg = "Strong password!";
            }

            return msg + ';' + pStrength;
        }
    }

    //default setting
    var password_settings = {
        minLength: 12,
        maxLength: 25,
        specialLength: 1,
        upperLength: 1,
        numberLength: 1,
        barWidth: 200,
        barColor: 'Red',
        specialChars: '!@#$', //allowable special characters
        metRequirement: false,
        useMultipleColors: 0
    };

    //password strength plugin
    $.fn.password_strength = function(options) {

        //check if password met requirement
        this.metReq = function() {
            return password_settings.metRequirement;
        }

        //read password setting from xml file
        $.ajax({
            type: "GET",
            url: "PasswordPolicy.xml", //use absolute link if possible
            dataType: "xml",
            success: function(xml) {

                $(xml).find('Password').each(function() {
                    var _minLength = $(this).find('minLength').text(),
                    _maxLength = $(this).find('maxLength').text(),
                    _numsLength = $(this).find('numsLength').text(),
                    _upperLength = $(this).find('upperLength').text(),
                    _specialLength = $(this).find('specialLength').text(),
                    _barWidth = $(this).find('barWidth').text(),
                    _barColor = $(this).find('barColor').text(),
                    _specialChars = $(this).find('specialChars').text(),
                    _useMultipleColors = $(this).find('useMultipleColors').text();

                    //set variables
                    password_settings.minLength = parseInt(_minLength);
                    password_settings.maxLength = parseInt(_maxLength);
                    password_settings.specialLength = parseInt(_specialLength);
                    password_settings.upperLength = parseInt(_upperLength);
                    password_settings.numberLength = parseInt(_numsLength);
                    password_settings.barWidth = parseInt(_barWidth);
                    password_settings.barColor = _barColor;
                    password_settings.specialChars = _specialChars;
                    password_settings.useMultipleColors = _useMultipleColors;
                });
            }
        });

        return this.each(function() {

            //bar position
            var barLeftPos = $("[id$='" + this.id + "']").position().left +
				$("[id$='" + this.id + "']").width();
            var barTopPos = $("[id$='" + this.id + "']").position().top +
				$("[id$='" + this.id + "']").height();

            //password indicator text container
            var container = $('<span></span>')
            .css({ position: 'absolute', top: barTopPos - 6,
		left: barLeftPos + 15, 'font-size': '75%',
		display: 'inline-block', width: password_settings.barWidth + 40 });

            //add the container next to textbox
            $(this).after(container);

            //bar border and indicator div
            var passIndi = $('<div id="PasswordStrengthBorder"></div>
		<div id="PasswordStrengthBar" class="BarIndicator"></div>')
            .css({ position: 'absolute', display: 'none' })
            .eq(0).css({ height: 3, top: barTopPos - 16, left: barLeftPos + 15,
		'border-style': 'solid', 'border-width': 1, padding: 2 }).end()
            .eq(1).css({ height: 5, top: barTopPos - 14, left: barLeftPos + 17 }).end()

            //set max length of textbox
            //$("[id$='" + this.id + "']").attr('maxLength', password_settings.maxLength);

            //add the border and div
            container.before(passIndi);

            $(this).keyup(function() {

                var passwordVal = $(this).val(); //get textbox value

                //set met requirement to false
                password_settings.metRequirement = false;

                if (passwordVal.length > 0) {

                    var msgNstrength = password_Strength.getStrengthInfo(passwordVal);

                    var msgNstrength_array = msgNstrength.split(";"), strengthPercent = 0,
                    barWidth = password_settings.barWidth,
			backColor = password_settings.barColor;

                    //calculate the bar indicator length
                    if (msgNstrength_array.length > 1) {
                        strengthPercent = (msgNstrength_array[1] /
				password_settings.minLength) * barWidth;
                    }

                    $("[id$='PasswordStrengthBorder']").css
			({ display: 'inline', width: barWidth });

                    //use multiple colors
                    if (password_settings.useMultipleColors === "1") {
                        //first 33% is red
                        if (parseInt(strengthPercent) >= 0 &&
			parseInt(strengthPercent) <= (barWidth * .33)) {
                            backColor = "red";
                        }
                        //33% to 66% is blue
                        else if (parseInt(strengthPercent) >= (barWidth * .33) &&
				parseInt(strengthPercent) <= (barWidth * .67)) {
                            backColor = "blue";
                        }
                        else {
                            backColor = password_settings.barColor;
                        }
                    }

                    $("[id$='PasswordStrengthBar']").css({ display: 'inline',
			width: strengthPercent, 'background-color': backColor });

                    //remove last "," character
                    if (msgNstrength_array[0].lastIndexOf(",") !== -1) {
                        container.text(msgNstrength_array[0].substring
				(0, msgNstrength_array[0].length - 2));
                    }
                    else {
                        container.text(msgNstrength_array[0]);
                    }

                    if (strengthPercent == barWidth) {
                        password_settings.metRequirement = true;
                    }
                }
                else {
                    container.text('');
                    $("[id$='PasswordStrengthBorder']").css("display", "none"); //hide
                    $("[id$='PasswordStrengthBar']").css("display", "none"); //hide
                }
            });
        });
    };
})(jQuery);

Using the Code

Include a Textbox control, jQuery library and the plug-in into the web page. Change the txtPassword id to your desired id. Use this line of code "var myPlugin = $("[id$='txtPassword']").password_strength();" to call the plug-in. To check if the password met the password policy, call the metReq() function with this line of code " myPlugin.metReq()". Please refer to listing 2 for full details. The jQuery $("[id$='txtPassword']") selector will work with ASP.NET server control, so don't bother using my 'txtPassword.ClientID'. The bar color, width and password policy information can be modified through the XML file.

Listing 2
<div style="height:400px"><br />
<asp:label runat="server" id="lblPassword"
    AssociatedControlId="txtPassword">Enter Password:</asp:label>
<asp:TextBox ID="txtPassword" runat="server"></asp:TextBox><br />
<a id="passwordPolicy" href="#">Password policy

<asp:Button ID="btnSubmit" runat="server" Text="Submit" />

<br /><br />
<asp:Label ID="ResultLabel" runat="server" Text=""></asp:Label>
</div>

<script src="Script/jquery-1.4.4.min.js" type="text/javascript"></script>
<script src="Script/jquery.password-strength.js" type="text/javascript"></script>

<script type="text/javascript">
    $(document).ready(function() {
        var myPlugin = $("[id$='txtPassword']").password_strength();

        $("[id$='btnSubmit']").click(function() {
            return myPlugin.metReq(); //return true or false
        });

        $("[id$='passwordPolicy']").click(function(event) {
            var width = 350, height = 300, left = (screen.width / 2) - (width / 2),
            top = (screen.height / 2) - (height / 2);
            window.open("PasswordPolicy.xml", 'Password_poplicy',
	    'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top);
            event.preventDefault();
            return false;
        });

    });
</script>

We can use the XLST to display the contents of the PasswordPolicy.xml or write code to extract its contents. I preferred to use XLST to avoid writing additional code. Displayed below is the password policy page. If you want to learn more on how to display XML with XSLT, click here.

Figure 4

Password policy

Code Behind or Server-side Code

The regular expression that we are using is listed in listing 3. The numbers are adjustable and come from the XML file.

Listing 3
(?=^.{12,25}$)(?=(?:.*?\d){2})(?=.*[a-z])(?=(?:.*?[A-Z]){2})
	(?=(?:.*?[!@#$%*()_+^&}{:;?.]){2})(?!.*\s)[0-9a-zA-Z!@#$%*()_+^&]*$

Shown in listing 4 is the code to generate regular expression dynamically. So, tomorrow if your client told you to increase the required numeric in the password policy, you don't have to search or create a new regular expression. All you have to do is change the setting in the PasswordPolicy.xml file. You can verify the regular expression here.

Listing 4
void btnSubmit_Click(object sender, EventArgs e)
{
    PasswordSetting passwordSetting = Helper.GetPasswordSetting();
    StringBuilder sbPasswordRegx = new StringBuilder(string.Empty);

    //min and max
    sbPasswordRegx.Append(@"(?=^.{" + passwordSetting.MinLength + "," +
		passwordSetting.MaxLength + "}$)");

    //numbers length
    sbPasswordRegx.Append(@"(?=(?:.*?\d){" + passwordSetting.NumsLength + "})");

    //a-z characters
    sbPasswordRegx.Append(@"(?=.*[a-z])");

    //A-Z length
    sbPasswordRegx.Append(@"(?=(?:.*?[A-Z]){" + passwordSetting.UpperLength + "})");

    //special characters length
    sbPasswordRegx.Append(@"(?=(?:.*?[" + passwordSetting.SpecialChars + "])
		{" + passwordSetting.SpecialLength + "})");

    //(?!.*\s) - no spaces
    //[0-9a-zA-Z!@#$%*()_+^&] -- valid characters
    sbPasswordRegx.Append(@"(?!.*\s)[0-9a-zA-Z" +
		passwordSetting.SpecialChars + "]*$");

    if (Regex.IsMatch(txtPassword.Text, sbPasswordRegx.ToString()))
    {
        ResultLabel.Text = "Password confront password policy!";
    }
    else
    {
        ResultLabel.Text = "Password does not confront password policy!";
    }
}

Question and Answer

Why do the progress bar indicators look different from the one in the demo application?

Make sure that there is a proper DocType declared before the <html> tag. Click here to learn more about it.

Figure 5

Password policy

How to change the TextBox to password mode?

For ASP.NET control, set the TextMode attribute to "Password". For HTML control, set the type property to "Password".

Why I'm getting the error "This type of page is not served." when running the Classic ASP code on the local ASP.NET Development Server?

Deploy the demo application to the IIS Web Application Server.

Can I use this plug-in with other programming languages?

Yes.

Display Different Colors in the Progressbar

I have received several suggestions from the reader to have different colors in the progress bar meter. I tried to implement this with the least possible change to the existing code. The current implementation does not have a complex algorithm to assign different weights to the characters. Here's how I implemented it, please refer to listing 5 for full details.

  • The colors are based on the password length.
  • If the password length is between 0 and 33 percent, display red color.
  • If the password length is between 33 and 67 percent, display blue color.
  • If above 67 percent, display the color specified in the PasswordPolicy.xml file.

I also added useMultipleColors attribute to the PasswordPolixy.xml to give users the option to enable or disable the multiple colors.

Listing 5
//use multiple colors
if (password_settings.useMultipleColors === "1") {
    //first 33% is red
    if (parseInt(strengthPercent) >= 0 &&
	parseInt(strengthPercent) <= (barWidth * .33)) {
        backColor = "red";
    }
    //33% to 66% is blue
    else if (parseInt(strengthPercent) >= (barWidth * .33) &&
	parseInt(strengthPercent) <= (barWidth * .67)) {
        backColor = "blue";
    }
    else {
        backColor = password_settings.barColor;
    }
}

Duplicate password strength indicator issue

Recently a user reported to me the issue with the Password Strength Indicator using jQuery and XML plug-in. The problem will occur (Figure 3.1) if the Label control ID contains the ID of the Textbox control (Listing 3.1). 

Listing 3.1
<asp:label runat="server" id="lblPassword" AssociatedControlId="Password">Enter Password:</asp:label>
<asp:TextBox ID="Password" runat="server"></asp:TextBox>
Figure 3.1

double bars

Solution: This issue is very rare but they are several solution we can apply to suppress it.

  1. Exclude the id attribute from the Label control
  2. Make sure that the Label control ID does not contain the ID of the Textbox/input control
  3. Modify this line:
  4. var myPlugin = $("[id$='txtPassword']").password_strength();

    to:

    var myPlugin = $("input[id$='txtPassword']").password_strength();

Duplicate password strength indicator issue: Update #2

Couple weeks ago a CodeProject member reported that the "Password Strength Indicator using jQuery and XML plug-in" is displaying duplicate indicators. Yesterday, I was trying to integrate the plug-in with the MVC 3 application and ran into the mentioned issue.

Figure 4.1

After spending some time digging into it, I noticed that there was a problem with the jQuery ID selectors. I should use the Attribute Equals Selector [id="value"] instead of Attribute Ends with Selector [id$="value"]. The former selector selects elements that have the specified attribute with a value exactly equal to a certain value. The later selector selects elements that have the specified attribute with a value ending exactly with a given string. That explain why the bar indicator appears next to both the password and confirm password textboxes. I have updated the plug-in and here is the correct approach to use the plug-in.

Listing 4.1
<script type="text/javascript">
    $(document).ready(function () {
        var myPlugin = $("input[id='Password']").password_strength();

        $("[id='submit']").click(function () {
            return myPlugin.metReq(); //return true or false
        });

        $("[id='passwordPolicy']").click(function (event) {
            var width = 350, height = 300, left = (screen.width / 2) - (width / 2),
            top = (screen.height / 2) - (height / 2);
            window.open("PasswordPolicy.xml", 'Password_poplicy',
	    'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top);
            event.preventDefault();
            return false;
        });
    });
</script>

Conclusion

I hope someone will find this information useful and make your programming job easier. If you find any bugs or disagree with the contents or want to help improve this article, please drop me a line and I'll work with you to correct it. I would suggest downloading the demo and explore it in order to grasp the full concept of it because I might miss some important information in this article. Please send me an email if you want to help improve this article.

History

  • 01/09/2011 - First release (v01.00.00)
  • 01/13/2011 - v01.01.00 release
    • Set the width of the text container to the width of the meter bar + 40 pixel
    • Display missing lower case character correctly
    • Include the source code for the plug-in
  • 01/20/2011 - v01.02.00 release
    • Added useMultipleColors attribute to PasswordPolicy.xml file. 1=Yes, 0=No
    • Added new logic in the plug-in to display background color in red if the strength is between 0 and 33%. The background color will be blue if the strength is between 33% and 67%.
  • 07/23/2012 - v01.03.00 release

Resources

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here