. 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
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 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:
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:
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;