Tutorial

Password Strength Meter in AngularJS

Draft updated on Invalid Date
Default avatar

By Glad Chinda

Password Strength Meter in AngularJS

This tutorial is out of date and no longer maintained.

Introduction

In this tutorial, we would be creating a simple registration form with just fields for fullname, email, and password. We would use zxcvbn to estimate the strength of the password in the form and also provide visual feedback. We would also use AngularJS for effortless two-way data bindings.

At the end of the tutorial, the final page will behave as shown in the following live demo:

View Password Strength on JSFiddle

Why Measure Password Strength?

Passwords are commonly used for user authentication in most web applications and as such, it is required that passwords be stored in a safe way. Over the years, techniques such as one-way password hashing - which involves salting most of the time, have been employed to hide the real representation of passwords being stored in a database.

Although password hashing is a great step in securing passwords, the user still poses a major challenge to password security. A user who uses a very common word as a password makes the effort of hashing fruitless since a bruteforce attack can crack such a password in a very short time. In fact, if highly sophisticated infrastructure is used for the attack, it may even take split milliseconds, depending on the password complexity or length.

Many web applications today such as Google, Twitter, Dropbox, etc insist on users having considerably strong passwords either by ensuring a minimum password length or some combination of alphanumeric characters and maybe symbols in the password.

How then is password strength measured? Dropbox developed an algorithm for a realistic password strength estimator inspired by password crackers. This algorithm is packaged in a Javascript library called zxcvbn. In addition, the package contains a dictionary of commonly used English words, names, and passwords.

Getting Started

Before we begin the tutorial, we would download all the dependencies we need using the Bower package manager. If you don’t already have Bower in your system, you can follow the Bower Installation Guide. Run the following command to install all the dependencies for the tutorial.

  1. bower install zxcvbn angularjs#1.5.9 bootstrap

The root folder should contain two folders and one file.

  • assets folder
  • bower_components folder
  • index.html file

The folder structure for our project should look like the following screenshot - you can create the folders and directories as required.

Project Screenshot

Building the HTML

Let’s begin by adding the basic markup for our page in the index.html file. We would link to the Bootstrap files - bootstrap.min.css and bootstrap-theme.min.css, and also the angular.min.js framework from our bower_components folder. We would also link to our project’s css and js files in the assets folder. See the following code for the basic HTML markup of our page.

index.html
<!DOCTYPE html>
<html class="no-js">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Password Strength</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
        <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap-theme.min.css">
        <link rel="stylesheet" href="assets/css/main.css">
    </head>
    <body>
        <div class="main-container">
        <div class="form-container">
        
            <form action="" method="POST" role="form">
                <legend class="form-label">Join the Team</legend>
            
                <div class="form-group">
                    <label for="fullname">Fullname</label>
                    <input type="text" class="form-control" id="fullname" 
                    placeholder="Enter Fullname">
                </div>

                <div class="form-group">
                    <label for="email">Email</label>
                    <input type="email" class="form-control" id="email" 
                    placeholder="Enter Email Address">
                </div>
            
                <div class="form-group">
                    <label for="password">Password</label>

                    <div class="form-hint">To conform with our Strong Password policy, you are 
                        required to use a sufficiently strong password. Password must be more than 
                        7 characters.</div>

                    <input type="password" class="form-control" id="password" 
                    placeholder="Enter Password">
                </div>

                <button type="submit" class="btn btn-primary">Submit</button>
            </form>

        </div>
        </div>
       
        <script src="bower_components/angular/angular.min.js"></script>
        <script src="assets/js/app.js"></script>
    </body>
</html>

At this point, our page should look like the following screenshot:

Initial Page

Building the CSS

Now we would add some CSS rules to the assets/css/main.css file to spice up the page.

main.css
body {
    margin: 0;
    padding: 0;
}

.main-container {
    display: table;
    width: 400px;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}

.form-container {
    position: relative;
    bottom: 100px;
    display: table-cell;
    vertical-align: middle;
}

.form-container form > div {
    padding: 0 15px;
}

.form-container form > button {
    margin-left: 15px;
}

legend.form-label {
    font-size: 24pt;
    padding: 0 15px;
}

.form-hint {
    font-size: 7pt;
    line-height: 9pt;
    margin: -5px auto 5px;
    color: #999;
}

At this point, our page should look like the following screenshot:

Page with CSS

Loading zxcvbn asynchronously

Now we would asynchronously load the zxcvbn package into our page. We would add the script in the assets/js/app.js file. The following script programmatically creates a new <script> element that is inserted before the first script element defined in the page, when the page is finished loading. The src of this script element points to the zxcvbn.js file. The async attribute is also set to true to enable asynchronous loading.

app.js
(function() {
    var ZXCVBN_SRC = 'bower_components/zxcvbn/dist/zxcvbn.js';

    var async_load = function() {
        var first, s;
        // create a <script> element using the DOM API
        s = document.createElement('script');

        // set attributes on the script element
        s.src = ZXCVBN_SRC;
        s.type = 'text/javascript';
        s.async = true; // HTML5 async attribute

        // Get the first script element in the document
        first = document.getElementsByTagName('script')[0];

        // insert the <script> element before the first in the document
        return first.parentNode.insertBefore(s, first);
    };

    // attach async_load as callback to the window load event
    if (window.attachEvent != null) {
        window.attachEvent('onload', async_load);
    } else {
        window.addEventListener('load', async_load, false);
    }
}).call(this);

Now we can try using the zxcvbn() function with any password string from our browser’s console. The zxcvbn() function returns a result object with several properties. In this tutorial, we would be concerned only with the score property, which is an integer from 0 - 4 (useful for implementing a strength bar).

  • 0 - too guessable
  • 1 - very guessable
  • 2 - somewhat guessable
  • 3 - safely unguessable
  • 4 - very unguessable
console.log(zxcvbn('password'));

See the following video on testing the zxcvbn() method on the browser’s console.

Mixing-in some AngularJS

Now we would make some little improvements to our code to engage AngularJS. First, let’s create a module for our app called PasswordStrength, and a simple controller for our form called FormController. We would append the following script to the assets/js/app.js file.

// creating the app module
angular.module('PasswordStrength', []);

// adding a controller to the module
angular.module('PasswordStrength').controller('FormController', function($scope) {});

Next, we would edit our index.html file to add an ng-app directive for the module on the root <html> element, and an ng-controller directive for the controller on the <form> element.

<!-- adding the ng-app directive -->
<html class="no-js" ng-app="PasswordStrength">

<!-- adding the ng-controller directive -->
<form action="" method="POST" role="form" ng-controller="FormController">

Validating the Form

Now we can add our validation logic to the form.

  • First, we would give names to the form and the input fields, and also add ng-model directives to the input fields to be able to harness the built-in NgModelController.
  • Next, we would add the ng-required constraint to the input fields, since we desire they should be filled.
  • Next, we would disable the submit button using the ng-disabled directive and only enable it when the form is fully validated.
  • Finally, we would harness the ng-class and ng-show directives to provide error feedback and messages for the input elements.

The modified form in our index.html file should look like this:

<form action="" method="POST" name="joinTeamForm" role="form" ng-controller="FormController">
    <legend class="form-label">Join the Team</legend>

    <div class="form-group">
        <label for="fullname">Fullname</label>

        <div class="error form-hint" ng-show="joinTeamForm.fullname.$dirty 
        && joinTeamForm.fullname.$error.required" ng-cloak>{{"This field is required."}}</div>

        <input type="text" class="form-control" ng-class="(joinTeamForm.fullname.$dirty && 
        joinTeamForm.fullname.$invalid) ? 'error' : ''" id="fullname" name="fullname" 
        placeholder="Enter Fullname" ng-required="true" ng-model="fullname">
    </div>

    <div class="form-group">
        <label for="email">Email</label>

        <div class="error form-hint" ng-show="joinTeamForm.email.$dirty && 
        joinTeamForm.email.$error.required" ng-cloak>{{"This field is required."}}</div>

        <div class="error form-hint" ng-show="joinTeamForm.email.$dirty && 
        joinTeamForm.email.$error.email" ng-cloak>{{"Email is invalid."}}</div>

        <input type="email" class="form-control" ng-class="(joinTeamForm.email.$dirty && 
        joinTeamForm.email.$invalid) ? 'error' : ''" id="email" name="email" 
        placeholder="Enter Email Address" ng-required="true" ng-model="email">
    </div>

    <div class="form-group">
        <label for="password">Password</label>

        <div class="form-hint">To conform with our Strong Password policy, you are required to use 
            a sufficiently strong password. Password must be more than 7 characters.</div>

        <input type="password" class="form-control" ng-class="(joinTeamForm.password.$dirty && 
        joinTeamForm.password.$invalid) ? 'error' : ''" id="password" name="password" 
        placeholder="Enter Password" ng-required="true" ng-model="password">
    </div>

    <button type="submit" class="btn btn-primary" ng-disabled="joinTeamForm.$invalid">Submit</button>
</form>

In the preceding code, we have named our form as joinTeamForm and have given names to the input elements making it possible for us to harness Angular’s built-in NgModelController. We have also created data-bindings for the input elements using the ng-model directive.

We used some of the validation state properties - $dirty, $invalid, $error, provided by the NgModelController API to determine if our form is fully validated. The ng-class directive was also used to dynamically add an error class to the input elements based on the validation criteria.

Also, we used ng-cloak to prevent the browser from showing our error messages while rendering. Since we included angular.js at the end of our page, this would not be effective. To correct this, we would add the following CSS rule to the main.css file.

[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
    display: none !important;
}

Now, we would add some CSS rules to the main.css file for our error feedback.

.form-control.error {
    border-color: red;
}

.form-hint.error {
    color: #C00;
    font-weight: bold;
    font-size: 8pt;
}

At this point, our page should look like the following screenshot - observe that the form submit button is now disabled on page load.

Page with validation

Adding the Password Strength Meter

Now we would go ahead to create the password strength meter. We would also create a new directive in our module called okPassword, that will define a custom validation constraint for the password element, which will ensure that a valid password must be more than 7 characters and must have a minimum zxcvbn score of 2. We will also add visual feedback to keep track of the password length.

First, let’s add the following into our index.html file, immediately after the password field element, for our password strength meter.

<div class="label password-count" ng-class="password.length > 7 ? 'label-success' : 'label-danger'" 
    ng-cloak>{{ password | passwordCount:7 }}</div>

<div class="strength-meter">
    <div class="strength-meter-fill" data-strength="{{passwordStrength}}"></div>
</div>

Here, we are using the ng-class directive and Bootstrap’s label classes to provide feedback based on the password length. We are also using a custom passwordCount filter to slightly format the display of the password length.

Also, we are binding the value for the data-strength attribute to the passwordStrength property of the controller’s scope, which will contain the password strength score.

Now, let’s add the following CSS rules to the main.css file to style the password strength meter which we just created.

.password-count {
    float: right;
    position: relative;
    bottom: 24px;
    right: 10px;
}

.strength-meter {
    position: relative;
    height: 3px;
    background: #DDD;
    margin: 10px auto 20px;
    border-radius: 3px;
}

.strength-meter:before, .strength-meter:after {
    content: '';
    height: inherit;
    background: transparent;
    display: block;
    border-color: #FFF;
    border-style: solid;
    border-width: 0 5px 0 5px;
    position: absolute;
    width: 80px;
    z-index: 10;
}

.strength-meter:before {
    left: 70px;
}

.strength-meter:after {
    right: 70px;
}

.strength-meter-fill {
    background: transparent;
    height: inherit;
    position: absolute;
    width: 0;
    border-radius: inherit;
    transition: width 0.5s ease-in-out, background 0.25s;
}

.strength-meter-fill[data-strength='0'] {
    background: darkred;
    width: 20%;
}

.strength-meter-fill[data-strength='1'] {
    background: orangered;
    width: 40%;
}

.strength-meter-fill[data-strength='2'] {
    background: orange;
    width: 60%;
}

.strength-meter-fill[data-strength='3'] {
    background: yellowgreen;
    width: 80%;
}

.strength-meter-fill[data-strength='4'] {
    background: green;
    width: 100%;
}

Here, we have made our password strength meter to indicate five levels for the different password strength scores ranging from 0 to 4. We have also specified different colors and fill widths for each score level.

At this point, our page should look like the following screenshot. Observe that the page exhibits some strange behavior since we have not yet defined the passwordCount filter.

Page with strength meter

Before we proceed, we would define the passwordCount filter which slightly formats the display of the password length in the view. Let’s append the following to the app.js file to create the filter.

// creating the passwordCount filter
angular.module('PasswordStrength').filter('passwordCount', [function() {
    return function(value, peak) {
        var value = angular.isString(value) ? value : '',
        peak = isFinite(peak) ? peak : 7;

        return value && (value.length > peak ? peak + '+' : value.length);
    };
}]);

In the preceding code, the passwordCount filter takes a string value and an optional peak parameter which defaults to 7 if omitted or not a valid integer. If the length of the input string is less than the peak, it returns the length of the string; otherwise, it returns {peak}+.

Now we can get visual feedback for our password length as we type. See the following screenshot:

Page with password count

Now, we would go on to create the okPassword directive for the password field. We would also create a service that will encapsulate the implementation of the zxcvbn() function. Let’s add the following to the app.js file to create the service.

// creating a service to provide zxcvbn() functionality
angular.module('PasswordStrength').factory('zxcvbn', [function() {
    return {
        score: function() {
            var compute = zxcvbn.apply(null, arguments);
            return compute && compute.score;
        }
    };
}]);

Here, we have created a service called zxcvbn that provides just one API method score(). The score() method takes the same parameters as the zxcvbn() function and calls it internally. It returns the estimated password strength score.

Now, we can apply this service to create our directive. Add the following to the app.js file to create the directive.

// creating the okPassword directive with zxcvbn as dependency
angular.module('PasswordStrength').directive('okPassword', ['zxcvbn', function(zxcvbn) {
    return {
        // restrict to only attribute and class
        restrict: 'AC',

        // use the NgModelController
        require: 'ngModel',

        // add the NgModelController as a dependency to your link function
        link: function($scope, $element, $attrs, ngModelCtrl) {
            $element.on('blur change keydown', function(evt) {
                $scope.$evalAsync(function($scope) {
                    // update the $scope.password with the element's value
                    var pwd = $scope.password = $element.val();

                    // resolve password strength score using zxcvbn service
                    $scope.passwordStrength = pwd ? (pwd.length > 7 && zxcvbn.score(pwd) || 0) 
                    : null;
                    
                    // define the validity criterion for okPassword constraint
                    ngModelCtrl.$setValidity('okPassword', $scope.passwordStrength >= 2);
                });
            });
        }
    };
}]);

In the preceding code, we defined the okPassword directive with zxcvbn service as a dependency. We specified that the directive can be used either as a class or an attribute. We also specified that we need the NgModelController.

In the link function, we added an event listener on the element which is triggered on blur, change, and keyup events. The event listener uses the $scope.$evalAsync() method to delay the update of the scope’s properties. Also, the $setValidity() method of the NgModelController was used to define the validity criterion for the okPassword validation constraint.

Finally, we go ahead to add the ok-password attribute or class to our password element to ensure that the validation constraint defined in our directive is applied.

<input type="password" class="form-control ok-password" ng-class="(joinTeamForm.password.$dirty && 
joinTeamForm.password.$invalid) ? 'error' : ''" id="password" name="password" 
placeholder="Enter Password" ng-required="true" ng-model="password">

Now we can get visual feedback for our password strength as we type. See the following screenshot:

Page with password strength

Conclusion

In this tutorial, we have been able to implement a password strength meter based on the zxcvbn Javascript library in our AngularJS application. For a detailed usage guide and documentation of the zxcvbn library, see the zxcvbn repository on GitHub.

For a complete code sample of this tutorial, checkout the password-strength-demo repository on GitHub. You can also get a live demo of this tutorial on jsFiddle.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Glad Chinda

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel