Passwords in HTML Forms: Allow the Client to Show or Hide

Published:
Updated:
Introduction
If you're like most people, you have occasionally made a typographical error when you're entering information into an online form.  And to your consternation, the browser remembers the error, and offers to autocomplete your future entries by using the typographical error.  This is somewhat annoying, and if you're paying attention, you can visually identify the error.  You can usually get past this issue by telling the browser to forget your recent history.  But have you ever begun typing your password or passphrase into an online form and thought, "Did I get that right?"  You can't visually check your work because the browser is showing you a form input field with something like this: ········· The browser is "helpfully" hiding the password from prying eyes by masking the input control field.

This article teaches three ways to allow your clients to show or hide the password fields as they type them into your forms.  In my experience, the default behavior that hides the password input is only marginally useful.  Most of the time nobody is looking over your shoulder and the real risk of lost passwords does not rest with the human client or the client browser, but with the servers where passwords are stored.  We can't change the server's security, but we can choose to make life easier for our (human) clients!

How HTML <input type="password"> Works
HTML defines several different input types for different kinds of input controls, including checkboxes, text, hidden, and password.  These input types are designated in the type= attribute of the <input> control.  The input controls all work the same way during communication with the server.  After the form is submitted, the request contains all of the name= attributes and the value= attributes associated with each named input control.  What makes the text and password input controls different is the way the browser displays the characters you enter into these controls.  The text control is rendered in clear text by the browser, however the password control is masked, usually by replacing each of the characters you type with a dot-character.  This does not change the internal contents of the input field - it only impairs your ability to see and verify what you're typing.

Toward a More Common-Sense Approach
It would seem reasonable that our clients would know whether it is safe to display their passwords, and the ability to display or hide their passwords on demand would be a useful feature of our user-experience.  Here are three approaches that bring about this functionality.  The first example will display the password inline, inside the form, by changing the input control attribute from password (where the characters are masked) to text (where the characters are displayed).  The second example will display the password in clear text in the web page, leaving the contents of the form's display unchanged.  The third example will work like Apple products, displaying only the last letter that you have typed, and obscuring the letters before it.  These examples rely on the popular JavaScript library, jQuery for its convenient syntax that allows us to identify and change elements of the web document object model.

Example #1: Show or Hide the Password in the HTML Form
Let's look at the behaviors of the HTML form, then we will see the code that produces the behaviors.

When the document is initially loaded we will see the password placeholder cuing us to enter a password.
pwrif0.pngOnce we type the password, we can see that it is masked by the standard behavior of the password attribute.
pwrif1.pngWhen we check the "Show Password" box, the browser will reveal the password in the HTML control.
pwrif2.pngWhen we uncheck the "Show Password" box, the browser will mask the password again.
pwrif3.pngHere is the code snippet that we used to demonstrate these behaviors.  Although these snippets both rely on the "checked" attribute of the "Show Password" checkbox, this snippet is a little more complicated than the next example.  This is because some browsers do not allow JavaScript to make direct modifications to the contents of HTML input controls (probably a security feature).  Because of this limitation, and the need for consistent cross-browser behavior, jQuery does not allow this either.  As a result, we cannot just change type="password" into type="text" but instead we must clone the input control, set its type attribute, insert the clone after the original input control, then remove the previous input control.  It's somewhat contorted, but effective.
<!DOCTYPE html>
                      <!-- demo/password_reveal_inform.html -->
                      <html dir="ltr" lang="en-US">
                      <head>
                      <meta charset="utf-8" />
                      <meta name="robots" content="noindex, nofollow" />
                      <meta name="viewport" content="width=device-width, initial-scale=1.0">
                      
                      <style type="text/css">
                      /* STYLE SHEET, AS NEEDED, HERE */
                      </style>
                      
                      <script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
                      <script>
                      $(document).ready(function(){
                      
                          $("#pwinput").focus();
                      
                          $("#pwcheck").click(function(){
                              if ($("#pwcheck").is(":checked"))
                              {
                                  $("#pwinput").clone()
                                  .attr("type", "text").insertAfter("#pwinput")
                                  .prev().remove();
                              }
                              else
                              {
                                  $("#pwinput").clone()
                                  .attr("type","password").insertAfter("#pwinput")
                                  .prev().remove();
                              }
                          });
                      });
                      </script>
                      
                      <title>HTML5 Page With jQuery in UTF-8 Encoding</title>
                      </head>
                      <body>
                      
                      <noscript>Your browsing experience will be much better with JavaScript enabled!</noscript>
                      
                      <p>
                      <input type="password" id="pwinput"  placeholder="Enter Password" />
                      <input type="checkbox" id="pwcheck" />Show Password
                      </p>
                      
                      </body>
                      </html>

Open in new window


Example #2: Show or Hide the Password in the Web Page
Again, we first look at the behaviors of the HTML form, then we will see the code that produces the behaviors.

When the document is initially loaded we will see the password placeholder cueing us to enter a password.
pwrit0.pngOnce we type the password, we can see that it is masked by the standard behavior of the password attribute.
pwrit1.pngWhen we check the "Show Password" box, the browser will reveal the password in a <span> of the HTML.
pwrit2.pngWhen we uncheck the "Show Password" box, the browser will hide the password.
pwrit3.pngHere is the code snippet that we used to demonstrate these behaviors.

<!DOCTYPE html>
                      <!-- demo/password_reveal_intext.html -->
                      <html dir="ltr" lang="en-US">
                      <head>
                      <meta charset="utf-8" />
                      <meta name="robots" content="noindex, nofollow" />
                      <meta name="viewport" content="width=device-width, initial-scale=1.0">
                      
                      <style type="text/css">
                      #pwtext{
                          font-weight:bold;
                          background-color:yellow;
                      }
                      </style>
                      
                      <script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
                      <script>
                      $(document).ready(function(){
                      
                          $("#pwinput").focus();
                      
                          $("#pwcheck").click(function(){
                              var pw = $("#pwinput").val();
                              if ($("#pwcheck").is(":checked"))
                              {
                                  $("#pwtext").text(pw);
                              }
                              else
                              {
                                  $("#pwtext").text("");
                              }
                      
                          });
                      });
                      </script>
                      
                      <title>HTML5 Page With jQuery in UTF-8 Encoding</title>
                      </head>
                      <body>
                      
                      <noscript>Your browsing experience will be much better with JavaScript enabled!</noscript>
                      
                      <p>
                      <input type="password" id="pwinput"  placeholder="Enter Password" />
                      <input type="checkbox" id="pwcheck" />Show Password <span id="pwtext"></span>
                      </p>
                      
                      </body>
                      </html>

Open in new window


Example #3: Like Apple Devices, Show Only the Last Character as We Type the Password
Our password input control starts with the familiar placeholderepw1.pngAs we begin to type we can see the first characterepw2.pngAs we continue to type, we obscure the characters, showing only the last characterepw3.pngWhen we have finished typing we see only the last character. epw4.pngHitting Enter or the submit button will submit the password to the action script, where it can be processedepw5.pngHere is the code snippet that demonstrates the Apple-like behavior.  It is more complicated than the other scripts because we must process each character as it is entered, and we must account for "paste" and "backspace" events.  This design may not work correctly in older versions of Internet Explorer, but it has a nice effect in the browsers that support it. 

The design uses two input controls.  One of the controls is hidden - it always contains the true text of the password in its current state, as it is being typed or pasted.  The other control is visible - it contains the partially obscured password with the last character visible in clear text.

There are three important jQuery functions.  These handle "paste", "keystroke", and "backspace."

The first jQuery function handles "paste" functionality.  For better or worse, the onPaste() routines get control before the paste operation has modified the input control field.  This means we cannot see what is being pasted in the onPaste() method!  In order to get the string we must go to the clipboard data and copy it.  Once we have captured the clip, we append it to the contents of the hidden control and update the value of the hidden control.  Then we create a string of stars, plus the last character of the hidden control value, and put this obscured representation into the visible control.

The second jQuery function handles "keystroke" (normal typing) functionality.  It is invoked each time a keyPress() event occurs.  When I originally tried this script, I tried keyUp() events, but found that keyPress() gave true information for the character being typed.  The keyUp() event gives only keyboard mapping characters - the upper and lower case characters map to the same key on the keyboard, and inside keyUp() you cannot tell them apart.

Each keyPress() event sends a single character to the function in the event.which property.  We ignore some of the keys because they are not useful or have special meanings such as Enter (meaning "submit").  Then we use the event.PreventDefault() method to keep the browser from putting the character into the visible input control.  Instead, we capture the character, append it to the contents of the hidden control and update the stored value of the hidden control.  Then we create a string of stars, plus the last character of the hidden control value, and put this obscured representation into the visible control.

The third jQuery function handles the "backspace" (erase typing) functionality.  The backspace key is explicitly ignored in the keyPress() handler and only processed in the keyUp() handler.  We get the string value from the hidden control value, remove the last character (if any) and update the hidden control.  Then we create the obscured password and put this information into the visible control.

One last thing to note - the form element contains an onSubmit() method.  This method removes the partially obscured visible control from the request input, leaving only the password-for-action in the request.  Our test script displays this field, showing the submitted value of the password we typed.

Here is the code that demonstrates the last-character-showing behavior:
<?php // demo/password_reveal_intype.php
                      /**
                       * Demonstrate how to reveal only the last character of a password
                       * as it is being typed, similar to the way Apple devices work
                       *
                       * Using paste to collect input strings
                       * Using keypress to collect input characters
                       * Using keyup to handle backspace
                       */
                      error_reporting(E_ALL);
                      
                      $req = NULL;
                      if (!empty($_POST)) $req = print_r($_POST, TRUE);
                      
                      $html = <<<EOD
                      <!DOCTYPE html>
                      <!-- demo/password_reveal_intype.html -->
                      <html dir="ltr" lang="en-US">
                      <head>
                      <meta charset="utf-8" />
                      <meta name="robots" content="noindex, nofollow" />
                      <meta name="viewport" content="width=device-width, initial-scale=1.0">
                      
                      <style type="text/css">
                      /* Stype Sheet Here */
                      </style>
                      
                      <script type="text/javascript" src="https://code.jquery.com/jquery-latest.min.js"></script>
                      <script>
                      
                      /* Clear Extraneous Display-Only Input on Submit */
                      function pw_clear_input(){
                          $("#pwinput").remove();
                      }
                      
                      $(document).ready(function(){
                      
                          /* Only One Input Control on this Page */
                          $("#pwinput").focus();
                      
                          /* Handle "Paste" Functionality */
                          $("#pwinput").on("paste", function(event){
                              var star = "*", hidn, text, long, char;
                              var clip = undefined;
                      
                              event.preventDefault();
                      
                              /* The Prior Input, if Any */
                              hidn = $("#pwhiddn").val();
                      
                              /* Capture Clipboard before Paste Finishes */
                              if (window.clipboardData && window.clipboardData.getData){ // IE
                                  clip = window.clipboardData.getData('Text');
                              }
                              else{
                                  var clipboardData = (event.originalEvent || event).clipboardData;
                                  if (clipboardData && clipboardData.getData){
                                      clip = clipboardData.getData('text/plain');
                                  }
                              }
                      
                              /* Concatenate Clipboard to Existing Input (if any) and Save in Hidden Element */
                              hidn = hidn + clip;
                              $("#pwhiddn").val(hidn);
                      
                              /* Create Obscured Password - Last Character Showing */
                              long = hidn.length - 1;
                              char = hidn.substr(long,1);
                              text = star.repeat(long) + char;
                      
                              $("#pwinput").val(text);
                          });
                      
                          /* Handle Each New Character of Input */
                          $("#pwinput").on("keypress", function(event){
                              var star = "*", hidn, text, long, char;
                      
                              // Ignore Backspace, Return (Enter), End, Delete Keys */
                              if (event.which == 8)  return;
                              if (event.which == 10) return;
                              if (event.which == 13) return;
                              if (event.which == 35) return;
                              if (event.which == 46) return;
                      
                              event.preventDefault();
                      
                              /* The Prior Input, if Any */
                              hidn = $("#pwhiddn").val();
                              char = String.fromCharCode(event.which);
                      
                              /* Concatenate Character to Existing Input (if any) and Save in Hidden Element */
                              hidn = hidn + char;
                              $("#pwhiddn").val(hidn);
                      
                              /* Create Obscured Password - Last Character Showing */
                              long = hidn.length - 1;
                              char = hidn.substr(long,1);
                              text = star.repeat(long) + char;
                      
                              $("#pwinput").val(text);
                          });
                      
                          /* Handle Backspace Character(s) */
                          $("#pwinput").on("keyup", function(event){
                              var star = "*", hidn, text, long, char;
                      
                              if (event.which != 8) return; // Only handle backspace
                      
                              event.preventDefault();
                      
                              /* The Prior Input, if Any */
                              hidn = $("#pwhiddn").val();
                      
                              /* Remove Character from Existing Input (if any) and Save in Hidden Element */
                              long = hidn.length - 1;
                              if (long >= 0){
                                  hidn = hidn.substr(0,long);
                                  $("#pwhiddn").val(hidn);
                              }
                      
                              /* Create Obscured Password - Only last Character Showing */
                              long = hidn.length - 1;
                              if (long >= 0){
                                  char = hidn.substr(long,1);
                                  text = star.repeat(long) + char;
                              }
                      
                              $("#pwinput").val(text);
                          });
                      });
                      </script>
                      
                      <title>HTML5 Page With jQuery in UTF-8 Encoding</title>
                      </head>
                      <body>
                      
                      <noscript>Your browsing experience will be much better with JavaScript enabled!</noscript>
                      
                      <p>
                      $req
                      <form id="pwform" method="post" onSubmit="return pw_clear_input()">
                      <input name="pw_4_display" id="pwinput" placeholder="Enter Password" type="text" autocomplete="off" />
                      <input name="pw_4_action"  id="pwhiddn" type="hidden" />
                      </form>
                      </p>
                      
                      </body>
                      </html>
                      EOD;
                      
                      echo $html;

Open in new window



Summary
This article has shown how to take the standard HTML input type="password" and modify its behavior to make a better user experience.  Only a few lines of JavaScript (jQuery) are needed -- well, maybe more than a few in the last example.  The standard password masking behavior is mostly preserved.  Our human client is now in control of whether the browser masks or displays the password fields.  This means that our human client can visually verify a password or passphrase while it is being entered and before it is sent to the server -- a capability that is not available in native HTML.  Through modest but thoughtful extensions of native behavior we can improve the way our web sites treat our clients.

If you're interested in this topic, you may also be interested in PHP Client Authentication! and Password Hashing.

Please Give E-E Your Feedback!
If you found this article helpful, please click the "thumb's up" button below. Doing so lets the E-E community know what is valuable for E-E members and helps provide direction for future articles.  If you have questions or comments, please add them.  Thanks!
 
5
18,090 Views

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.