Thursday, March 21, 2013

Adding the ability to "show password" to your login page

We're used to coding a login form using the input type "password" for passwords, which hides the password behind some sort of special character...usually a dot or asterisk...but, let's think about why hiding a password this way can be a problem.

Of course there are a couple of problems that this approach reveals that all roll up into it taking a little control away from the user. Under the current approach, if login fails, the user has to retype the password without knowing if there was a typo in the first request or if they're presenting what should be a valid password and not remembering that it's different. For people who are using small keyboards and for people who frequently change their passwords, these can be significant problems. Even worse, we usually have a retry counter tied to the login attempts to lock an account if there are too many invalid attempts. We could easily lock accounts just because someone has difficulty using the keyboard on a mobile device - a completely legitimate activity.

What's the simple solution? Allow the user to make the choice to display the password on the page. Of course we're still going to encrypt it and all of that, so there's no real risk beyond someone peeking over their shoulder - and hopefully the user can manage to either prevent that piece of the activity or not display the password.

Of course doing this is almost ridiculously easy, but there is a little trick to it in order to get it to work correctly in Internet Explorer (yes, people are still using IE).


First, let's assume you have a page that contains a relatively standard login block something like this...

<fieldset>
  <legend>Login</legend>
  <div>
    <label for="username">Username</label>
    <input id="username" name="username" type="text" />
  </div>
  <div>
    <label for="pwd">Password</label>
    <input class="password" id="pwd" name="pwd" type="password"/>
  </div>
</fieldset>

Working from this, add a checkbox (because a radio button can't turned off once it's turned on) just below the password. Since this whole process is JavaScript dependent, you can feel free to use JavaScript to add the node in the appropriate place if you'd like, just be sure that the markup is accessible. You should have something like this in place of the original password block...

<div>
  <label for="pwd">Password</label>
  <input class="password" id="pwd" name="pwd" type="password"/>
  <label onclick="togglePassword(this);"><input type="checkbox"/>Show password</label>
</div>

I'm using the click event of the label because it's a sibling of the element that I want to change. Using a sibling makes traversing the tree, a time-consuming process, a little faster...and, because the click event bubbles, I don't have to do anything special when someone clicks on the box.

So, now all I need to do is handle the click event in the togglePassword function...which means I need a togglePassword function that's something like this...


function togglePassword(cbx) {
  if (cbx) {
    var pwd = cbx.previousElementSibling ? cbx.previousElementSibling : cbx.previousSibling,
    status = cbx.getElementsByTagName("input").item(0),
    node = document.createElement("input");
    /* Adjust for a text node being returned as the sibling */
    while (pwd.nodeType !== 1) {
      pwd = pwd.previousElementSibling ? pwd.previousElementSibling : pwd.previousSibling;
    }
    /* Check to make sure we have a handle on an input element and the checkbox */
    if (pwd && pwd.nodeName === "INPUT" && status) {
      /* Copy all the attributes on the element */
      node.className = pwd.className;
      node.id = pwd.id;
      node.name = pwd.name;
      node.type = status.checked ? "text" : "password"; /* Change the input type according to the checkbox */
      node.value = pwd.value;
      pwd.parentNode.replaceChild(node, pwd);
    }
  }
}

It would be nice if we could reduce this to find the password input and changing the type from password to text and back again based on whether or not the box had been checked, but IE doesn't allow the changing of input type after the element has been rendered.

Here's an example of how it works


Login

Anyway, that's all there is to it - pretty simple, as I said. One little caveat - if you have listeners attached to the password input because you're doing something a little weasel-like - timing the typing pattern to try to create a user password-entry fingerprint, for example - you'll need to attach those listeners to the node variable before passing it into the replaceChild call, otherwise they won't work...but I'm sure you wouldn't be doing something that would degrade the user experience without benefit like that, right? After all, part of getting paid to think is thinking about what the user needs, not necessarily what makes our lives easier.

No comments:

Post a Comment