On password masking and usability

I just read Jakob Nielsen’s recent post in which he urged web designers/developers to stop password masking due to it’s inherent usability issues. I found it an interesting read. Hey, at last, someone dared to talk about the elephant in the room!

In most cases password masking is indeed useless, but still, there are several cases where you need that kind of protection. He also points that out, suggesting a checkbox to enable the user to mask their entered password if they wish to do so. He also suggests that checkbox being enabled by default on sites that require high security.

I think the checkbox idea is really good, as long as it works in the opposite way: Password masking should always be the default and you should check the checkbox to show the characters you typed. This is in line with what Windows (Vista or newer) users are already accustomed to anyway:

Enter passphrase

This can (and should) be done with JavaScript alone: if the user has it turned off, no problem, just a regular old password field. Of course the checkbox should also be dynamically added, to prevent users with disabled JS from viewing a checkbox that does nothing at all.

This seems easy at first, even without a library (although, in this particular case, a library would greatly reduce the amount of code required, so much that I’m tempted to include a jQuery version as well):

window.onload = function() {
	var passwords = document.getElementsByTagName('input');
	for(var i=0; i<passwords.length; i++) {
		if(passwords[i].type == 'password') {
			var password = passwords[i];

			var showCharsCheckbox = document.createElement('input');
				showCharsCheckbox.type = 'checkbox';
				showCharsCheckbox.onclick = (function(input) {
					return function() {
						input.type = this.checked? 'text' : 'password';
					};
				})(password);

			var showCharsLabel = document.createElement('label');
				showCharsLabel.appendChild(showCharsCheckbox);
				showCharsLabel.appendChild(document.createTextNode('Show characters'));

			// If the password field is inside a <label> element, we don't want to insert our label in there as well!
			var previousSibling = /label/i.test(password.parentNode.nodeName)? password.parentNode : password;

			// Check whether it's the last child of it's parent
			if(previousSibling.nextSibling) {
				previousSibling.parentNode.insertBefore(showCharsLabel, previousSibling.nextSibling);
			}
			else {
				previousSibling.parentNode.appendChild(showCharsLabel);
			}
		}
	}
}

However, nothing is ever simple, when you also need to support our beloved Internet Explorer. Most moderately experienced JavaScript developers have probably already understood what I’m talking about: The all time classic IE bug (still present in IE8…) in regards to setting an <input /> element’s type attribute. You can only set it once, for elements that are not already in the DOM. After that, it becomes read-only, and any attempt to set it results in a “The command is not supported” error. And when I say “any” attempt I mean it:

  • element.setAttribute()
  • element.type
  • element.setAttributeNode()
  • element.removeAttribute() and then element.setAttribute()
  • element.cloneNode(), then one of the above, then replacing the node with the clone

everything fails miserably.

I’ve encountered this problem several times in the past as well, but I could always think of an alternative way to do what I wanted without having to work around it. In this case, I don’t think there is one. So we’re left with two possible scenarios:

  • Perform an easy test in the beginning to see whether this bug exists and proceed only if the browser isn’t naughty. This could be done with the following:
    var supportsChangingTypeAttribute = (function() {
    	var input = document.createElement('input');
    	try {
    		input.type = 'password';
    		input.type = 'text';
    	} catch(e) {
    		return false;
    	}
    	return input.type == 'text';
    })();
    
    if(supportsChangingTypeAttribute) {
    	// do stuff...
    }
  • Wrap the statement that IE chokes on in a try…catch construct and in the catch(e) {…} block create a new input element, copy everything (where everything is at least: attributes, properties, event handlers – both traditional ones and listeners) from the password field into it (except the type attribute of course!) and replace the original password field with it. After the first time, the text field could also be reused, to improve performance.
    If you have a shortage of trouble in your life, you may attempt it, I currently do not. :P It can be a very simple task for particular cases, but a generic solution that would work in any site (or even in most sites) seems a really daunting, tedious and downright boring task. I also hope there might be a better solution, that I haven’t thought of. Any ideas?
  • http://www.wpfav.gr Basilakis

    Or we can leave IE outside our Designs, why not we? :)

  • http://leaverou.me Lea Verou

    It would be even better if we could leave IE outside of our minds as well. :P However, neither of those two is usually an option…

    We can just dream that one day IE will implement Webkit as a rendering engine and V8 or TraceMonkey as a JS engine (yeah, right… :( )

  • http://thorinmesser.com Thorin Messer

    Someone seems to have a shortage of trouble in life: http://www.unwrongest.com/projects/show-password/

  • http://leaverou.me Lea Verou

    Not really. I, myself said in the original post that it’s simple for particular cases (especially when event handlers are not involved).
    After looking into it and a bit of quick testing, it appears that the script you provided doesn’t copy event handlers to the text input, even the ones set via jQuery or traditional event handling (which is easy). The hard (and correct way) would be to copy ALL event handlers to the clone, no matter how they’re set (which may even be impossible).

    Just try that:

    $(function() {
    	$('#text').showPassword('#checkbox');
    	$('#text').click(function() { alert('foo'); });
    	$('#text').get(0).onclick = function() { alert('bar'); };
    });

    and see if they work on the text input as well (hint: they don’t).

    This is not trivial: Most authors that want something like that, also have event handlers on form elements for validation (in the password field for password strength for instance). This would break this kind of form validation.

    Moreover, that script requires the checkbox to be present, even when JavaScript is disabled, which is generally considered bad practice. The checkbox has to be generated with JavaScript, since it only functions when JavaScript is enabled.

    Interesting blog though, thanks. Just added it to my feed reader. :)

  • http://stackoverflow.com/users/56555/lucas-aardvark Lucas

    You could also just let users use there OpenID, that way they only have to remember one password.

  • http://www.wpfav.gr Basilakis

    No openID! Facebook Connent! :D

    Anyway, here Lea, a WordPress plugin to do that :)

    http://blog.clearskys.net/2009/06/30/unmask-password-plugin/

  • http://milw0rm.com Anotha Guest

    http://blog.decaf.de/2009/07/iphone-like-password-fields-using-jquery/

    Maybe you’ll find this interesting. An iphone-like password field mimic with jQuery. Not well written (early version) + it includes some important bugs, but if you’d like to implement something like this in your website, I hope the source code can inspire you to…

  • Pingback: Idea: The simplest registration form ever « Lea Verou

  • http://profiles.google.com/gaia.chik Gaia Canestrari

    thanks, this is really helpful. x

  • http://www.register-web-domain.in/ Domain name registration

    Really helpful post. thanks for sharing. And now i am going to bookmark this site.

  • Pingback: Hacking at 0300 : Unmask Password Plugin

  • http://www.facebook.com/rssaddict Louis Simoneau

    Given the password masking functionality is implemented by the browser, maybe browsers could also implement the ability to reveal the characters? Some kind of “show” icon that appears next to password fields?

  • Timo Kissing

    Instead of copying all the event handlers one could also leave the original input in the page and bind one handler for every possible event that ‘forwards’ the event using trigger()

  • http://cody-jones.com Mediumjones

    I really like the way Android 4.0+ displays the masked character for about 500ms after you type it. Just long enough for you to confirm you’ve hit the right key in this new age of soft keyboard confident users.

  • Pingback: On password masking and usability | Lea Verou » Web Design