Direct Login - Ajax Patterns

Direct Login

From Ajax Patterns

Evidence: 0/3

Tags: Access Account Authentication Login


Contents

In A Blink

Hash sent from browser.


Goal Story

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


Problem

How can users present their credentials to the server?


Forces

  • Login is a necessary evil - it should be as transparent as possible.
  • Casual users may not bother to log in if the process interrupts their browsing experience.
  • Login requires interaction with the server, in order to validate the username and password.
  • The password should not travel in plain-text, where it could be intercepted while it travels to the server.



Solution

Authenticate the user with an XMLHttpRequest Call instead of 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 interaction style. But as discussed below, there's a very useful, though optional, technique here, which involves Javascript-based hashing, and is specific to the login process.

As a background to this pattern, let's consider conventional alternatives. In the early days of the web, many applications handled authentication with standards-based HTTP Authentication, requiring users to enter username and password into a popup window. However, this approach was intrusive for mainstream applications, due to its all-or-none approach: either you're allowed in or you see nothing at all.

For this reason, submission-based authentication is usually a superior solution, and a modern standard. Here, the username and password are submitted via a standard HTML form. The server converts the password to a "hash" (or "validator") which must match the stored hash for that user. A hash is stored in the database, instead of the real password, because it's not possible for someone to log in by just knowing the user's hash. The process is secure as long as the username and password are sent over a secure connection (using the HTTPS protocol), though many websites simply send the details over plain-text.

However, submission-based authentication is still somewhat invasive, requiring submission to the server for validation. Even worse if you have to retrieve a lost password! In some cases, like when you're going online to pay a bill, you'll probably spend as much time as necessary to login. But, for casual browsing, the effort of logging in might not seem worthwhile. Any technique that can shave a few seconds off login time is a boon for users and the website operator alike, as logged-in users have additional permissions and can receive personalised content.

Direct Login allows for the smooth user experience characteristic of Ajaxian applications - there is no form submission and the browser display is just manipulated according to server response.

In the simplest approach, you can implement Direct Login by simply sending the password (along with the username) as plain-text, using an XMLHttpRequest POST to the validation service. Then, the server behaves similar to a conventional server: checks if the password matches using a hash function, and prepares for session management. Exactly how session management happens is specific to the server platform. In Java, for instance, the standard approach would be to attach an HttpSession object to the inbound HttpServletRequest object, which will usually result in a cookie scheme being used. In other environments, cookies might be directly manipulated by the program. In any event, XMLHttpRequest deals with cookies as with regular form-handling, so this will allow the session to be established in the same way as a standard form submission. The only difference is the response content: instead of outputting a new HTML page, the server will be outputting an XML or plain-text acknowledgement, and potentially a few details such as last login time.

Now, passing the credentials over with XMLHttpRequest will improve usability, but with the password still being transferred in plain-text, there's still a security threat. Ensuring the whole transaction runs overs HTTPS is the best measure, as it generally makes it secure from interception. However, many websites don't provide such a facility. Fortunately, there's a technique that can be used to prevent transmission of plain-text passwords. The technique is, strictly speaking. orthogonal to the direct login approach - you could apply it to conventional submitted-based authentication. 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 capable and fast enough to transform the password into a hash value, and there are libraries around to implement popular algorithms. The naieve way to do it is would be to just replicate the server process, and send across the hash as stored in the database. The server then just checks if the hashes match. But any interceptor would then be able to perform a "replay attack", logging using the same details. So we need a more sophisticated approach, leading to the following "double hashing" algorithm.

With double hashing, the server generates a one-time random seed (or "challenge"). The browser then hashes twice: first, it hashes the password to yield what's hopefully stored on the database. But instead of sending that, the browser combines it with the one-time seed to form a new hash. This new hash is sent to the server. The server then pulls out the stored hash from the database and combines it with the same one-time seed to form a new hash, which must match the hash that was uploaded. This works because in both cases, the initial password 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. And 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 website.
  • 2. Server handles initial page.
    • 2a. Server generates one-time seed (S) and stores it.
    • 2b. Server outputs page, including login form, and with one-time seed embedded somewhere on the page (e.g. in a Javascript variable).
  • 3. User enters username (U) and password (P).
  • 4. Browser handles submission.
    • 4a. Browser hashes password (P) using permanent hash function, to arrive at the attempted hash value (Ha) that should be held in the database.
    • 4b. Browser combines attempted hash (Ha) with one-time seed (S) to create one-time, double-hashed, value (Da).
    • 4c. Browser uploads username (U), double-hashed value (Da), and (for convenience) one-time seed (S).
  • 5. Server authenticates.
    • 5a. Server verifies one-time seed (S) is valid.
    • 5b. Server extracts stored hash for this user (H) and combines it with the seed (S) to get one-time, double-hashed, value (D).
    • 5c. 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 not, it either re-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 the server, so you'll need a portable algorithm. Two popular standards are MD5 and SHA-1, and both have implementations on Javascript and just about any server-side language you're likely to use. Performance is unlikely to be a significant issue, as the hash function only needs to be invoked once per session (assuming the login is successful).

How will you manage the one-time seed?

The double-hashing algorithm hinges on the one-time seed being used once only, and ensuring the user authenticates with the seed that the server provided. There are a few decisions bere:

  • How does the seed expire? In theory, its lifetime shouldn't matter much since it will only be used once - there's no risk of someone intercepting a successful upload and reusing that data to authenticate. However, if all the seeds are stored in memory, you'll probably want to clear any unused seeds, e.g. once an hour. More important than lifetime is number of validation attempts - perhaps you only want to allow three login attempts against the same seed. In this case, you'll need to associate a counter with the seed.
  • Is the session tracked? When confirming the seed, do you use a cookie to check that it was sent to this particular browser, or do you simply check that the seed was recently generated and sent somewhere? The first approach is safer, since there's less chance someone could guess the seed. Though it requires cookies, it's likely cookies will be subsequently be used for authentication purposes anyway. The second approach is less work and might be considered "safe enough" for some purposes.
  • Is the seed uploaded back to the server? The algorithm above requires the seed to 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 already having been downloaded in plain-text, there's no significant threat by uploading it back.
  • What if I require persistence? When a user enters another page on the site, all information will be lost. In traditional(Web 1.0) design, PHP is usually the preferred language for authentication. PHP stores browser sessions, however you cannot store a session with javascript, nor can you make an AJAX call to a PHP page and have it create or modify a session.

In this case you can store the seed with a user ID in a cookie on the client. This acts like more of a 'remember me' function, however it my be sufficient enough for most users' needs. Once authentication occurs on the server, the server stores that seed and the client IP address in a column on the users table, then delete the seed from its temporary location. When visiting another page with the same browser, the page would check for this cookie then send the user ID and seed to the server. The server would check the ID, seed, and IP address against the user column and verify that they are a match. For extra security, you can store a date of when the seed was stored so that you may have it expire. Another good security measure is to issue a new seed when the seed has matched successfully.

An example of the above pattern with this persistence function:

  • 1. User visits website.
  • 2. Server handles initial page.
    • 2a. Server generates one-time seed (S) and stores it.
    • 2b. Server outputs page, including login form, and with one-time seed (S) which is then stored in a cookie via javascript.
  • 3. User enters username (U) and password (P).
  • 4. Browser handles submission.
    • 4a. Browser hashes password (P) using permanent hash function, to arrive at the attempted hash value (Ha) that should be held in the database.
    • 4b. Browser combines attempted hash (Ha) with one-time seed (S) to create one-time, double-hashed, value (Da).
    • 4c. Browser uploads username (U), double-hashed value (Da), and (for convenience) one-time seed (S).
  • 5. Server authenticates.
    • 5a. Server verifies one-time seed (S) is valid.
    • 5b. Server extracts stored hash for this user (H) and combines it with the seed (S) to get one-time, double-hashed, value (D).
    • 5c. 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)
    • 5d. Server stores the original seed (S), the users ID, date, and client IP address in the user information table.
  • 6. User requests another protected page.
    • 6a. Page checks if the cookie exists. If so, client sends the user ID and the seed (S) to the server.
    • 6b. Server checks user ID, seed (S), and IP against the user information. If successful, it logs the user in (optionally server deletes seed out of user, generates a new seed, stores it in the table, then sends it to the client so that it may replace the seed in the cookie).


Real-World Examples

Ajax Login Demo

James Dam hosts a login demo which inspired this pattern. It demonstrates an XMLHttpRequest-based Direct Login along the Javascript encryption technique. You simply enter username and password in the form, and the result - whether logged in or not - is shown directly on the page.


Code Examples

Ajax Login Demo

James Dam's Ajax Login presents a standard HTML form. Submission is disabled and handled instead by callback methods, registered on initialisation.

  <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 intent to authenticate, 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 along with the seed itself, and both 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 hex_md5() is used twice, the double-hashing operation.

  // validateLogin method: validates a login request
  function validateLogin() {
    ...
    // compute the hash of 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 it had previously sent out, and checking if the hash value matches a hash of the seed and the stored password hash. If so, it deletes the seed to ensure it's only used 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 or not.


Alternatives

Submission-Based Authentication

As mentioned in the solution, submission-based authentication is the standard approach. The solution here will generally make the overall browsing experience smoother, and for applications where login is optional (e.g. E-Commerce websites), the pattern is beneficial by encouraging more users to login.


Related Patterns

Lazy Registration

Lazy Registration is focused on first-time registration as well as deferred login, and makes use of Direct Login.

Host-Proof Hosting

Host-Proof Hosting also uses Javascript to perform encryption-related functionality.

Timeout

Apply a Timeout to log users out.

Visual Metaphor

The traditional approach is like turning up for a meeting and having the receptionist go over to a manager to confirm your identity. In contrast, the Direct Submission approach is like having a security card that lets you go straight through and focus on the task at hand.

Want to Know More?

Acknowledgements