Better Passwords #1: The Masked Password Field
Password fields.
We all have to deal them, but they’re never a joy to use. Clearly they have to be obfuscated in some way–to keep our passwords safe from bypassers’ prying eyes–but when you’re unable to see what you’re typing, it’s easy to become hesitant, nervous even, and unsure of whether you’re entering the right information.
I’m an experienced typist, but I still type passwords with one finger! And some forms even ask you to enter them twice, just to be sure that you know what you entered, and that you entered it correctly.
Password fields are annoying. There has to be a better way.
Platform Solutions
Away from the Web, various platform solutions have been presented. Mac OS wireless-password fields have a “Show password” checkbox next to them, which allow you to view it in plain text; we’ll be looking at that solution for the next post in this series.
In this post we’re going to look at a solution more common among handheld devices. Recent Symbian and Android devices do this, but it’s arguably more commonly known on the iPhone and iPod touch.
The Masked Password Field
A masked password field obscures all the characters except the last one, which is visible as plain text. So you can always see what you’re typing, but the whole password is never revealed, making it near-impossible to read surreptitiously:
A login form with a masked password field.
I’m now going to show you a script that adds this behavior to ordinary "password"
fields.
The script is quite complex. The core of what it does is simple, but there are several browser-related and usability gotchas to consider, and these add some complications. So while I’ll try to give you a good sense of how it all works, this is less a how to
post, and more a whether to
overview. The real nitty-gritty is in the script itself, which is extensively commented with details of what does what, and why.
Have a look at the demo and grab the script. It works in all modern browsers, including IE6:
First: the Markup
To begin with, we need a "password"
type field. We have to do this as a progressive enhancement, so that unsupported or no-script browsers still have a proper password field:
<label for="pword">Password:</label>
<input type="password" id="pword" name="pword" />
The script will convert this to a "text"
field, as well as creating a second, "hidden"
field. The hidden field is where we’ll store the plain, unencrypted version of the password, while the visible field is what users type into, and where the masked password is shown.
So this is what the markup will ultimately become:
<label for="pword">Password:</label>
<span>
<input type="hidden" name="pword" />
<input type="text" class="masked" id="pword" autocomplete="off" />
</span>
You can see how only the hidden field retains the name
attribute, so that it’s the only one to be submitted. The visible field is what users see, but its value would be junk to any other process, so we stop it from being submitted, and (crucially) don’t allow autocomplete to remember it either (because all it would remember is a series of dots, with no way of decoding them).
Originally, I had an elegant process for doing this markup conversion, but naturally enough it failed to work in IE. In Internet Explorer, even the latest version, you’re unable to modify the "type"
attribute of a password field, or set the "type"
of any form field that was generated in the DOM. The only way to create form fields in IE is to write them out in source text and inject it into the page with innerHTML
! It’s ugly, I know, but it’s what it takes to make this work.
Then: the Events
Once we have the markup, we need to handle its events; we have to be able to respond to any kind of input, whether it’s by keyboard, mouse, or some programmatic means. There are so many ways, in fact, that it would be impossible to cater for them all in advance; but we can catch the changes retrospectively, and modify the value just after it’s been input. As long as we do it fast enough, users should be unable to notice the difference. To achieve this the script uses four event-listeners.
The first two provide a solid foundation, being the most robust and cross-browser compatible; these are: onkeyup
to catch primary keyboard input, and onchange
to catch secondary input such as pasting from the mouse.
Then we supplement them with some nonstandard events that are similar to onchange
, but which respond immediately instead of waiting for the field to lose focus; these are: oninput
(which works in the latest builds of the top browsers apart from IE), and onpropertychange
(which is IE-only). We have to be careful with these events, though, because they also fire instantly from programmatic input; we therefore must watch that our code doesn’t create an infinite loop, by causing an event that it also responds to.
So with that spread of event listeners, we’ve covered every base and can respond to user input by any method, virtually instantly in most cases. And when we do respond, that’s when the masking happens.
When new input arrives in the text field, the first task is to extrapolate the entire plain password from the data we already have, plus the new input. For example, if the hidden field contains the value "passw"
and the text field now has the value "●●●●wo"
, we can extrapolate–by counting letter by letter, from the first–that the new value is "passwo"
. Once we have that, we encode the whole value and write it back to the visible field so that it now says "●●●●●o"
, at the same time as updating the hidden field with the plain version, "passwo"
. (The dots themselves are a unicode character, 25CF
, which is what most browsers use for regular password fields.)
This is a repeating, reciprocal process: new input triggers decoding, which triggers re-encoding, and by this process we continually have both a masked and a plain version of the password; the form can be submitted at any time, and the hidden field will always reflect the masked value you can see.
Now for the Gotchas!
As we’ve just seen, encoding and decoding of the masked password happens by comparing two values character by character, but we can’t do that unless we know which character equates to which; for example, the dot at index [0]
corresponds with the letter at the same index. Ultimately, we can’t allow users to input characters at the beginning or middle of the password because that would break the equation; it’s only when input is limited to the end that we can know how the characters equate.
To implement this we have to limit the caret position, so the script has a routine which does precisely that–whenever the field has focus, it continually forces the caret to be at the end–so you can edit or select from the end, but not from the beginning or middle.
We’ll also encounter some security issues with regard to how browsers treat the value contained in the field. Since the field we’re working with is just a "text"
field, browsers give it no special consideration. With real "password"
fields, there are limits placed on how values carry over; for example, user input into a password field is not retained after soft refresh, or when coming back in the history; but with ordinary text fields, it is (or may be).
So we have to implement additional behaviors to take control of this. The script will delete any default value from the original field, and also trigger a reset()
event to remove any residual input after soft refresh. (You’ll also remember how we had to disable autocomplete on the field, because the value the browser would try to remember is just a bunch of dots.)
All things considered then, this implementation is far from straightforward. Like I said at the start–the core of it is simple, but the devil, as always, is in the detail.
Conclusion
A JavaScript solution for this is probably impossible to implement without any side effects. The browser is set up to deal with "text"
fields at a lower level of security than "password"
fields, and this difference is why extra hacks were needed to bring the masked field up to the same level. But more fundamentally, we need to break the physical link between the data in the visible field and its counterpart in the hidden field in order to remove the input restrictions, and that really requires a lower-level implementation (for example, being able to store data as properties of an individual character).
I think this kind of password field is a good idea, but browsers should be implementing it as an input
type in its own right.