Direct Login






Direct Login

Access, Account, Authentication, Login

Direct Login


Goal Story

When Stuart logs in to perform an online exam, he is presented with a standard username-password form. He enters his username and password, but a few seconds later, the form becomes red and shows an error message underneath. He switches off Caps Lock and re-enters his credentials. This time, they're accepted. The form morphs to show his name and balance, and a new menu appears alongside it.

Problem

How can users present their credentials to the server?

Forces

  • Login is a necessary evilit should be as transparent as possible.

  • Casual users may not bother to log in if the process interrupts their browsing experience.

  • Login requires the browser to interact with the server in order to validate the username and password.

  • The password should not travel in plain-text; it could be intercepted while traveling to the server.

Solution

Authenticate the user with an XMLHttpRequest Call instead of with a form-based submission, hashing in the browser for improved security. The essence of this pattern is a routine transformation from a submission-based approach to an Ajaxian, Web Remoting (Chapter 6) interaction style. But, in this pattern, I also discuss a very useful, though optional, technique that involves JavaScript-based hashing and is specific to the login process.

Conventional authentication usually requires the user and password to be uploaded as a standard form submission. The server usually converts the password to a hash (or "validator") value and checks it against a stored hash value in the database. If they match, the user is in.

There are two problems with this approach. Firstly, flushing the page can be a distraction. It might not take long, but it will usually leave the user in a different context, which will discourage her from logging in. Even more troublesome is the stream of pages that ensue when a password must be recovered (e.g., having to provide your maiden name, last purchase date, and the name of your favorite pet canary), especially in this security-conscious era. The other problem relates to security of the transmission; if the password is uploaded as plain-text, there's a risk of interception.

Direct Login addresses the page refresh problem and, optionally, the transmission problem too. In the simplest approach, you can implement Direct Login by simply sending the username and password in plain-text to the validation service using an XMLHttpRequest POST. Then, the server behaves similarly to a conventional server: it checks whether the password matches using a hash function and prepares for session management. XMLHttpRequest deals with cookies as it does regular form handling; this allows the session to be established in the same way as a conventional form submission. The only difference is the response content: instead of outputting a new HTML page, the server outputs an XML or plain-text acknowledgment, as well as any personalized content.

Passing the credentials over with XMLHttpRequest will improve usability, but as long as the password is being transferred in plain-text, there's still a security threat. Ensuring the whole transaction runs overs HTTPS is always the best measure, as this generally makes the transaction secure from interception. However, many web sites don't provide such a facility. Fortunately, there's a compromise that can prevent transmission of plain-text passwords. The technique, strictly speaking, is orthogonal to the Direct Login approach; you could apply it to conventional submission-based authentication as well. But since it makes heavy use of browser-side processing, it fits nicely with Direct Login.

The trick is to perform hashing in the browser. JavaScript is fast enough to transform the password into a hash value (not to mention that it's capable of doing it), and there are libraries available to implement popular algorithms.

The naïve way of hashing is to simply hash the password to match what should be in the database. But any interceptor would then be able to perform a replay attackit could log in using the same details. So we need a more sophisticated approach, such as the following double hashing algorithm.

With double hashing, the server generates a one-time random seed (S). The browser then hashes twice: first, it hashes the password (P), hopefully to yield what is stored on the database (Ha; Hash attempt). But instead of sending that, the browser combines it with the one-time seed to form a new hash (Da; Double-hash attempt). This new hash is sent to the server. The server then pulls out the stored hash (H) from the database and combines it with the original one-time seed (S) to form a new hash, which must match the hash (Da) that was uploaded. This works because, in both cases, the initial password (P) has been passed through the same two hash functions. In the browser, the user's attempt is passed through a fixed hash function, and the result is immediately passed to a new hash function with one-time seed. In the server, the database already holds the result of hashing the real password using the fixed hash function. As long as the server uses the same seed and algorithm as the browser used to perform a second hashing, the two results should match. The server is also responsible for clearing the one-time seed after a successful login; otherwise, an interceptor could log in later on by uploading the same data. Here's a summary of the algorithm:

  1. User visits web site

  2. Server outputs initial page.

    1. Server generates one-time seed (S) and stores it.

    2. Server outputs page, including login form, with one-time seed embedded somewhere on the page (e.g., in a JavaScript variable).

    3. User enters username (U) and password (P)

  3. Browser handles submission.

    1. Browser hashes password (P) using permanent hash function in order to arrive at the attempted hash value (Ha), which should be held in the database.

    2. Browser combines attempted hash (Ha) with one-time seed (S) to create one-time, double-hashed value (Da).

    3. Browser uploads username (U), double-hashed value (Da), and one-time seed (S).

  4. Server authenticates

    1. Verifies that one-time seed (S) is valid.

    2. Server extracts stored hash for this user (H) and combines it with the seed (S) to get one-time, double-hashed value (D).

    3. Server compares the double-hashed values (D and Da). If successful, it logs the user in (e.g., creates a new session and outputs a successful response code) and clears the one-time seed (S). If it is not successful, it either generates a new seed or decrements a usage counter on the existing seed.

Decisions

What hashing algorithm will be used?

You're going to be hashing in the browser as well as in the server, so you'll need a portable algorithm. Two popular standards are MD5 and SHA-1; both have implementations on JavaScript and on just about any server-side language you're likely to use.

How will you manage the one-time seed?

The double-hashing algorithm hinges on the one-time seed being used only once and on ensuring that the user authenticates with the seed that the server provided. You have to make a few decisions regarding this:


How does the seed expire?

In theory, the seed's lifetime shouldn't matter much since it will only be used oncethere's no risk of someone intercepting a successful upload and reusing that data to authenticate. However, you'll probably want to clear any unused seeds periodically, e.g., once a day. More important than lifetime is number of validation attemptsperhaps you want to allow only three login attempts against the same seed. In this case, you'll need to associate a counter with the seed.


Is the seed uploaded back to the server?

The algorithm above requires that the seed be uploaded, but the server could instead track the session with a unique session ID and use that to look up the most recent seed it sent out. It's probably better to upload the seed in most cases, as it keeps the conversation as stateless as possible. With the seed having already been downloaded in plain-text, there's no significant threat posed by uploading it again.

Real-World Examples

NetVibes

The NetVibes (http://netvibes.com) portal handles the entire login process without any page refreshes.

Protopage

The Protopage (http://protopage.com) portal pops up a login box without opening a new page, though it sends you to a new page after the credentials are submitted.

Treehouse

Treehouse Magazine (http://treehousemagazine.com/), has a sidebar with a login Microlink. When clicked, it expands to form a login area, which can in turn morph into a registration area. It also degrades to use standard form submission if JavaScript is disabled.

Code Examples: Ajax Login Demo

James Dam's Ajax Login (http://www.jamesdam.com/ajax_login/login.html) presents a standard HTML form (Figure). Submission is disabled and handled instead by callback methods registered on initialization:

Direct login demo


  <form action="post" onsubmit="return false">
    <div id="login" class="login">
      <label for="username">Username: </label>
      <input name="username" id="username" size="20" type="text">
      <label for="password">Password: </label>
      <input name="password" id="password" size="20" type="password">
      <p id="message">Enter your username and password to log in.</p>
    </div>
    <label for="comments">Comments:</label>
    <textarea rows="6" cols="80" id="comments"></textarea>
  </form>

As soon as the user signals his intent to authenticate, which is indicated by form field focus, a random, one-time seed is retrieved from the server if there isn't already one present. The response comes in two parts: an ID for the seed and the seed itself, both of which are saved as JavaScript variables. The server can later use the ID to retrieve the seed it sent:

  function getSeed( ) {
    ...
    if (!loggedIn && !hasSeed) {
        http.open('GET', LOGIN_PREFIX + 'task=getseed', true);
        http.onreadystatechange = handleHttpGetSeed;
        http.send(null);
    }
    ...
  }
  function handleHttpGetSeed( ) {
    ...
    if (http.readyState == NORMAL_STATE) {
        results = http.responseText.split('|');
        // id is the first element
        seed_id = results[0];
        // seed is the second element
        seed = results[1];
    }
    ...
  }

The seed is then used to hash the password upon submission. Notice that hex_md5( ), the double-hashing operation, is used twice.

  // validateLogin method: validates a login request
  function validateLogin( ) {
    ...
    // compute the hash of the password and the seed
    hash = hex_md5(hex_md5(password) + seed);

      // open the http connection
      http.open('GET',
        LOGIN_PREFIX +
        'task=checklogin&username='+username+'&id='+seed_id+'&hash=
'+hash, true);
      ...
  }

The server then validates by locating the seed that it previously sent out, and checking if the hash value matches a hash of the seed and the stored password hash. If it does, the server deletes the seed to ensure that it's used only once:

  sql = 'SELECT * FROM seeds WHERE id=' . (int)$_GET['id'];
  ...
  if (md5($user_row['password'] . $seed_row['seed']) == $_GET['hash']) {
      echo('true|' .  $user_row['fullname'']);
      ...
      mysql_query('DELETE FROM s WHERE id=' . (int)$_GET['id']);
  }

After calling for validation, the browser receives a response and the form is morphed to show whether login was successful.

Related Patterns

Lazy Registration

Lazy Registration (see earlier) is geared toward first-time registration as well as deferred login, and it makes use of Direct Login.

Host-Proof Hosting

Host-Proof Hosting (see the next pattern), like Direct Login, uses JavaScript to perform encryption-related functionality.

Timeout

Apply a Timeout (see later) to log users out.

Acknowledgments



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows