. Guidelines for Secure Password Input
Use the "password" input type
Instead of <input type="text"> you should always use
<input type="password"> as this lets the browser (and
the user) know that the contents of that field need to be secured. Your
password won't appear on the screen as you type and most browsers also
won't 'remember' the values entered in
password fields as they
do with other form elements.
Confirm password input
Because the
password input type obscures the text typed, you
should let the user confirm that they haven't made a mistake. The
simplest way to do this is to have the password entered twice, and then
check that they are identical.
Another method is to display what they've entered as part of a
'confirmation page'. The problem here is that you're making the
password visible in the browser, browser cache, proxy, etc. For
security a password should
never be displayed in HTML or even
sent by email.
Enforce 'strong' passwords
If you're concerned about security you should have some policy on
what constitutes a valid password. Some common restrictions are:
- at least n characters
- combination of upper- and lower-case characters
- one or more digits
- not related to other user data (name, address, username, ...)
- not a dictionary word
Leaving the last requirement for now, as it requires a server-side
script, let's see what's possible using just client-side HTML and
JavaScript.
Server security
While having a strong password is a good first step, it needs to be
backed up by additional measures on the server that prevent brute-force
attacks. One popular approach is to install Fail2Ban to monitor log
files and lock out repeat offendors. Of course that only works if your
login system reports failed login attempts to a system log file.
Otherwise your application needs to provide this function.
2. Basic Demonstration
The form below has three input fields: username, pwd1 and pwd2.
When the form is submitted the
checkForm script parses the
input values and returns either
true or
false. If a
false value is returned then the form submission is cancelled.
This code will work for browsers as far back as Netscape 4 (circa
1997).
The code behind the form is as follows. If you're not sure how to
place this on your page, you might need to read the preceding article on
Form Validation, or view the HTML
source of this page.
<script type="text/javascript">
function checkForm(form)
{
if(form.username.value == "") {
alert("Error: Username cannot be blank!");
form.username.focus();
return false;
}
re = /^\w+$/;
if(!re.test(form.username.value)) {
alert("Error: Username must contain only letters, numbers and underscores!");
form.username.focus();
return false;
}
if(form.pwd1.value != "" && form.pwd1.value == form.pwd2.value) {
if(form.pwd1.value.length < 6) {
alert("Error: Password must contain at least six characters!");
form.pwd1.focus();
return false;
}
if(form.pwd1.value == form.username.value) {
alert("Error: Password must be different from Username!");
form.pwd1.focus();
return false;
}
re = /[0-9]/;
if(!re.test(form.pwd1.value)) {
alert("Error: password must contain at least one number (0-9)!");
form.pwd1.focus();
return false;
}
re = /[a-z]/;
if(!re.test(form.pwd1.value)) {
alert("Error: password must contain at least one lowercase letter (a-z)!");
form.pwd1.focus();
return false;
}
re = /[A-Z]/;
if(!re.test(form.pwd1.value)) {
alert("Error: password must contain at least one uppercase letter (A-Z)!");
form.pwd1.focus();
return false;
}
} else {
alert("Error: Please check that you've entered and confirmed your password!");
form.pwd1.focus();
return false;
}
alert("You entered a valid password: " + form.pwd1.value);
return true;
}
</script>
expand code box
Remember that, as JavaScript isn't available in all browsers, you
should user server-side scripting to validate all data before recording
it in a database or text file. You might also want to spice up your
forms using HTML5 Form
Validation as we've done further down the page.
3. Advanced regular expressions
In the latest browsers - those that support JavaScript 1.5 (Firefox,
Netscape 6/7, Mozilla, Safari and Opera 7) - you can use more powerful
regular expressions. Older browsers
will not recognise these
patterns so the following is mostly useful for intranet rather than
Internet applications.
The code presented above is fine in that it checks everything that we
wanted to check, but uses a lot of code to test each requirement
individually and present different error messages. We're going to show
you now how to apply the password tests using a single regular
expression.
Consider the following:
<script type="text/javascript">
// at least one number, one lowercase and one uppercase letter
// at least six characters
var re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}/;
var validPassword = re.test(input);
</script>
The type of expression used here is called a 'look-ahead' which tries
to match the contained regexp against the 'future' part of the
string.
Translation:
- matches a string of six or more characters;
- that contains at least one digit (\d is shorthand for [0-9]);
- at least one uppercase character; and
- at least one lowercase character:
input | result of test | reason |
abcABC | false | no numbers |
abc123 | false | no uppercase characters |
abAB1 | false | too short |
abAB12 | true | - |
Aa123456 | true | - |
If you are using a supported browser you can use the form
below to test the regular expression:
If you want to restrict the password to ONLY letters and numbers (no
spaces or other characters) then only a slight change is required.
Instead of using
. (the wildcard) we use
\w:
<script type="text/javascript">
// at least one number, one lowercase and one uppercase letter
// at least six characters that are letters, numbers or the underscore
var re = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])\w{6,}$/;
var validPassword = re.test(input);
</script>
The
\w is shorthand for 'any letter, number or the
underscore character'.
Again, you can use the form below to test this regular expression:
4. Sample HTML and JavaScript code
You might implement this code on your own website as follows:
<script type="text/javascript">
function checkPassword(str)
{
var re = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])\w{6,}$/;
return re.test(str);
}
function checkForm(form)
{
if(form.username.value == "") {
alert("Error: Username cannot be blank!");
form.username.focus();
return false;
}
re = /^\w+$/;
if(!re.test(form.username.value)) {
alert("Error: Username must contain only letters, numbers and underscores!");
form.username.focus();
return false;
}
if(form.pwd1.value != "" && form.pwd1.value == form.pwd2.value) {
if(!checkPassword(form.pwd1.value)) {
alert("The password you have entered is not valid!");
form.pwd1.focus();
return false;
}
} else {
alert("Error: Please check that you've entered and confirmed your password!");
form.pwd1.focus();
return false;
}
return true;
}
</script>
<form method="POST" action="form-handler.php" onsubmit="return checkForm(this);">
<p>Username: <input type="text" name="username"></p>
<p>Password: <input type="password" name="pwd1"></p>
<p>Confirm Password: <input type="password" name="pwd2"></p>
<p><input type="submit"></p>
</form>
As you can see, it's well worth learning the intricacies of regular
expressions. They can be used not just in JavaScript, but also PHP,
Perl, Java and many other languages. Some text editors (not just vi)
also allow them when searching for or replacing text.
5. HTML5 Form Validation
We earlier mentioned HTML5 form validation. This is a new technique
available in modern browsers and definitely the way of the future. A
few simple form attributes can have the same effect as reams of
JavaScript code libraries.
Here we have an enhanced version of the above code where we've added
HTML5
required and
pattern elements to apply regular
expression tests within the form itself in supporting browsers.
Helpfully the regular expression syntax is identical with just the
/^ and
$/ removed.
We've also added a tricky little
onchange handler to the
first password field which updates the pattern required by the second
password field - in effect forcing them to be identical:
Here you can see a screen shot from Safari of the form being
completed. The red/green markers have been implemented using CSS:
In this example it should be clear to the user that the form can only
be submitted once all three green ticks appear. In any case browsers
such as Firefox and Opera will enforce the HTML5 validation rules and
present messages as shown here:
Presumably the browser messages will change according to the users
language - something that would never be possible using only
JavaScript.
All we have changed from the previous example is to add some extra
attributes to the form input fields. The rest of the HTML and
JavaScript remains unaltered:
...
<p>Username: <input type="text" required pattern="\w+" name="username"></p>
<p>Password: <input type="password" required pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])\w{6,}" name="pwd1" onchange="form.pwd2.pattern = this.value;"></p>
<p>Confirm Password: <input type="password" required pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])\w{6,}" name="pwd2"></p>
...
The best thing about HTML5 attributes is that they have no effect
whatsoever on unsupported browsers, so Internet Explorer will act as if
they are not present and simply run the JavaScript as before.
At this stage both Firefox and Opera enforce HTML5 validation
attributes in the browser while Safari only lets you use them in
combination with CSS effects.
6. Customised HTML5 browser alerts
As you can see from the screenshot above the alert message in Firefox
for when the input doesn't match the
pattern attribute is
simply "Please match the requested format.". Not entirely
helpful in this case where we have a number of different
requirements.
Fortunately it is possible to customise this message using just a
touch of JavaScript.
The only change between this and the previous example is that we've
modified the
onchange handler for the Password input and added
one for Confirm Password:
...
<p>Password: <input type="password" required pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])\w{6,}" name="pwd1" onchange="
this.setCustomValidity(this.validity.patternMismatch ? 'Password must contain at least 6 characters, including UPPER/lowercase and numbers' : '');
if(this.checkValidity()) form.pwd2.pattern = this.value;
"></p>
<p>Confirm Password: <input type="password" required
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])\w{6,}" name="pwd2" onchange="
this.setCustomValidity(this.validity.patternMismatch ? 'Please enter
the same Password as above' : '');
"></p>
...
When the Password input is changed we check its
validity.patternMismatch flag to see whether it matches the
pattern attribute. If it doesn't match we set a custom error
message to appear when the form is submitted.
Whenever
setCustomValidity() has been used to set a custom
message the field in question will be regarded as invalid and prevent
the form from submitting (at least in Firefox and Opera). To reverse
this it needs to be set to blank, which we do when the input matches our
regex rules.
The custom message we have set appears in Firefox as shown here:
You can see from the code that we have applied a similar technique to
the Confirm Password field so it will now display "Please enter the
same Password as above" if there is a mismatch between the two
fields. Basically what we've achieved here is to replicate our
JavaScript validation script using HTML5.
Because we are only checking for
patternMismatch we are not
affecting the other default validation options - namely the
required attribute - so submitting the form with blank fields
will still display the generic "Please fill out this field"
alert message. To override those errors you would need to check the
validity.valueMissing flag.
7. Conclusions
As you can see there's a lot involved in providing a rich user
experience even for something as simple as changing a password. To
summarise what we've covered:
- Always start by emailing new users a random password or unique activation link;
- Use the password input type to prevent passwords appearing on screen;
- Decide what constitutes a 'strong' password for your system and enforce it:
- server-side for spambots and users with JavaScript disabled;
- using JavaScript for browsers that don't support HTML5 validation;
- using HTML5 for a more user-friendly experience;
- Use CSS rules to highlight valid/invalid input for browsers that don't have alerts built-in; and
- Customise the HTML5 error messages where appropriate for improved usability;
Most of all don't feel you have to install massive JavaScript or
jQuery libraries just to validate a form. By taking advantage of new
browser standards as show in this article you can save time and
resources and at the same time provide a better user experience.