Skip to page content or skip to Accesskey List.
Search evolt.org
evolt.org login: or register

Work

Main Page Content

AJAX Login System using XMLHttpRequest

Rated 3.64 (Ratings: 5) (Add your rating)

Log in to add a comment
(28 comments so far)

Want more?

 
Picture of ucahg

ucahg

Member info | Full bio

User since: April 12, 2001

Last login: February 23, 2009

Articles written: 3

The idea of Asyncronous Javascript and XML (Ajax) has been around in different forms for some time now, but only recently is truly taking off. If you aren't familiar with what I'm talking about, feel free to peruse Adaptive Path's recent article on ajax, but it is not required reading.

In a nutshell, ajax encompasses the idea of using XMLHttpRequest objects in javascript to avoid needlessly refreshing a web page. Famous uses of it are Google Suggest and Google Maps, as well as many other non-Google websites that I cannot recall offhand. Possibilities are growing that go beyond what an iframe and DHTML could handle, let alone pure page refreshes. The Internet is finally truly going stateless, and the challenge posed now by the adaptive path article is as follows: to forget what we think we know about the limitations of the Web, and begin to imagine a wider, richer range of possibilities.

In that regard, I started a project on the weekend that I wasn't sure was possible: creating a fully secure "ajax"-powered login system, ideal for blogs, forums, and other similar sites. I had a barebones secure case working within a few hours, and a few more hours gave the final result that I will share today.

Before we go on, I suggest you check out the demonstration of the login system trying out the username and password combinations "user1 / pass1" and "user2 / pass2". Once you get a grasp for what is going on, we'll continue.

So in essence the system does exactly what you just saw, and exactly what I described. While I will not go through the code, I'll briefly go over how the system as a whole works:

  1. You signal that you intend to log in by focussing on the username or password text box on the page.
  2. The server then obtains a random number ("seed" in the code) for the transaction that will be used only for the current transaction, and once the transaction is complete, the seed is useless. (Note this means that if data is intercepted, it cannot be reconstructed to log in the user that was intercepted.)
  3. Once you enter a username and password, the server md5 hashes your password, and then md5 hashes that hash with the seed, and sends thi to the server for authentication (along with your username and the id of the transaction).
  4. The server compares the hash it recieved with the hash of the password hash stored in the database concatenated with the seed for the transaction given by the id from the client.
  5. If these two hashes match, the user is logged in. Otherwise, the appropriate error message is sent back to the client.

Notice how I haven't discussed the presentation issues, as in reality they have little to do with the problem. When I moved from my barebones ugly example to the somewhat more aesthetically pleasing one that I've linked to today, I didn't change any of the backend, nor the login_controller.js file. In designing the system this way, it can be applied to any number of applications, such as a blog comment, a forum, etc.

Also notice that this is seemingly more secure than a traditional login system since the password is never transmitted in plain text.

In the example I have given, I didn't provide any allowances for older browsers, however it would be very simple to modify it such that it degrades gracefully.

Finally, I didn't actually use XML anywhere in my implementation. It simply wasn't necessary, plain text served just as well. In more complicated situations XML might be the answer, but don't over-complicate the problem.

It is my hope that this application of ajax and XMLHttpRequest gets your creativity going for more applications of the technology, and makes you more aware of just how cool it can be.

The last line of the Adaptive Path article I referenced to at the beginning of this article says "It's going to be fun." So far it's been great fun for me, and I trust it will be for you too.

Good Idea

Submitted by sam.hill on January 13, 2006 - 17:08.

Nice job! A nice addition would be to indicate acceptance of the credentials without having to shift focus away from the password textbox.

login or register to post comments

Possibly misleading hype

Submitted by everling on January 14, 2006 - 05:58.

I'd just like to point out that your idea is effectively the same as what HTTP Digest Authentication does.

Source: <a href="http://www.ietf.org/rfc/rfc2617.txt" rel="nofollow">http://www.ietf.org/rfc/rfc2617.txt</a>

Also, you do not need or require Ajax or XMLHTTPRequest to implement this idea. I've been doing this with good old DHTML for a few years already. It also appears that Yahoo's own login implementation does the same.

You can get the MD5 or the SHA-1 hash functions implemented in Javascript at <a href="http://pajhome.org.uk/crypt/md5" rel="nofollow">http://pajhome.org.uk/crypt/md5</a>.

Please don't add more unjustified hype to Ajax.

login or register to post comments

In fact not misleading...

Submitted by Hawhill on January 16, 2006 - 12:18.

Hey, did you try out the demo? It's not about challenge-response auth (albeit using it), but in fact a cool demonstration how feedback forms and other actions that require an authentication for typically only one action can benefit from AJAX. (In case you missed it: The user doesn't have to click on any login page and doesn't need to explicitly click on a "login" button. But the user will know if his authentication did succeed). Thanks to the article's author, interesting thing!

login or register to post comments

Sorry, Not Secure

Submitted by rbb36 on January 17, 2006 - 15:59.

Security is shockingly more difficult than I realized when I first started working with it. Like every software engineer new to security, I made mistakes on almost every piece of software I wrote initially. That is to say, this is not intended as an indictment of your work, which is quite good. It's just that security is extremely hard.

When you use AJAX from a page which was accessed on a non-SSL connection, the subsequent XMLHttpRequest calls are also not SSL encryped. Therefore, the login data is traversing the wire in clear text. As such, there is at least one attack that will break this, and another that probably would depending on what happens next on the page.

A man-in-the-middle attack would allow Mallory (the man in the middle) to read the authentication request, send it to the server, receive the authentication response, and forward the response back to the client. The man in the middle now has all the info the client has, and can impersonate at will. This attack is hard since it requires compromising one of the nodes between the client and server, but it does happen in the real world (particularly where the data to be secured is highly valued). Since the intervening network must be considered untrusted on the internet (and even on many or most corporate LANs), man-in-the-middle must be considered a real risk.

Depending on what happens after login, a sniffing attack may work as well. I did not look closely at the code to see how the authenticated identity of the user is being stored on the client and sent back to the server in subsequent calls. Presumably there must be some token that the client sends to the server which allows the server to recognize a subsequent connection from the now authenticated client. Often this token is stored in a cookie, or since you're using DHTML it could be stored in a variable. It is also possible (but highly inadvisable) to use the client's IP address as the token. Regardless of what type of token is used, the sniffer will see it: If it is a token sent in the response to the login XMLHttpRequest (such as would be stored in a cookie or DHTML variable) that token can be sniffed on the way to the client and played back to the server before the client gets a chance to use it (so even one-time tokens won't work). If it is the client IP address the attacker can forge his or her packets with that IP address.

To jump to the chase: In order to get secure authentication between any client and any server, they must at a minimum; have a shared secret, use public key cryptography, or communicate on a secure channel. To apply that general rule to the web, note that a shared secret between web client and server is very hard to establish without a secure channel. A secure channel, of course, is not possible on the web without encryption (done by public key or shared secret). That pretty much leaves public key crypto for web clients. The easiest form to use is HTTPS/SSL, since the browser and XMLHttpRequest naturally support it. Once you have established a secure SSL pipeline, the double hashing is not necessary.

Sorry to be the bearer of bad news, and I hope I don't put you off security as it is clear that you have a good mind for it. I am only hoping I can help you jump forward a few steps where I had to learn by trial and error. One method that has helped me a great deal is to wear two hats; first write the system, then try to attack it. Try to think of all the ways that an enemy with unlimited resources would attempt to violate the system.

login or register to post comments

It's "Privacy Enhanced" not Secure as in SSL secure

Submitted by mel on January 20, 2006 - 20:35.

I think secure in this case is used to indicate that your password cannot be captured... certainly it is not secure in terms of an overall session.

What the code does, is prevent an observer, or even a (limited) man-in-the middle attack from obtaining the user password.

Mallory cannot recover the password, without attempting a dictionary/brute force attack, or reversing the MD5. Even though Mallory knows the salt, and the algorithm, Mallory gains zero-knowledge of the password itself. As a man in the middle, Mallory's only chance is to, change the login mechanism (hashing script) sent to the client, to get the password in plaintext. Otherwise, even if Mallory were to change the seed and or ID, the returned hash would not reveal the password itself. And thus not allow Mallory to login, even for one session.

Replay attacks are thwarted by generating a new salt for the MD5 on each login/session challenge.

Mallory can however replay/use a validated session key to gain access for the life of that one session key.

login or register to post comments

Danny

Submitted by danja on February 3, 2006 - 11:14.

This is very welcome, unfortunately with the demo page, whenever I try user1/pass1 I get: Unknown error (hacking attempt). user2/pass2 : either the above error or just "undefined"

login or register to post comments

hacking attempt and undefined

Submitted by jrevillini on March 8, 2006 - 15:31.

Danny, make sure you create the mysql database and set the credentials in the login.php script.

login or register to post comments

implementation details

Submitted by jrevillini on March 8, 2006 - 15:54.

please don't make the mistakes that i did. here are a few details that help you get going to implement this on your own.

1) md5 the password when inputing it into your database - INSERT INTO users (username, password, fullname) VALUES ('user1', md5('pass1'), 'Sample User');

2) getting the login.php code:
a) click the link for login.php (it will download as login.phps (on ff1.5 anyway))
b) rename this to login.html and open it in a browser.
c) copy the code you can see in the browser into a new file called login.php and save it in the same directory with the other files.
d) make sure you delete any spaces and/or newline characters outside the
<?php
and
?>
, otherwise you will never successfully log in.

login or register to post comments

undefined

Submitted by Hendrik on April 23, 2006 - 23:16.

I also checked the demo out but I am getting for all what I enter Undefined. I did follow all the above steps. Any ideas?

login or register to post comments

also undefined

Submitted by jackinloadup on May 12, 2006 - 22:46.

i am also having the same problem as Hendrik. i have followed all the steps and not matter what it still says undefined. i have tried php 4 and 5. error i think is coming from xml_http_request.js but i dont know enough about javascript to fix it. any help would be great

login or register to post comments

Another undefined

Submitted by frustrated123 on May 15, 2006 - 15:51.

I havent had a successful login yet

Have tried all of the above suggestions but keep getting 'undefined' or 'Unknown Error (Hacking attempt)'

I have tried various types of user/pass but none seem to work - i think it may be to do with the seeds process, as no entries are ever in this table in my db - or should they be deleted?

login or register to post comments

=(

Submitted by bahzin on May 25, 2006 - 04:36.

I made everything, but it doesnt work =(

login or register to post comments

Using RSA

Submitted by lpezet on July 12, 2006 - 21:34.

For those not satisfied with the seed stuff, some guys use asymetric encryption (RSA here) in both login and sign up.
They first request the public key from the server and then encrypt (in javascript) the password and send the credential to the server.
Looks pretty nifty...(just rate the movie and the login/registration will show up)
<a href="http://nycfilms.org/watch.do?pmc=P6&videoID=6" rel="nofollow">Ajax Login/Register @ nycfilms.org</a>

login or register to post comments

Insecure

Submitted by lwz on December 26, 2006 - 04:15.

As someone has pointed out, the above is not secure, since if I had access to the database, I could just use the hashed password to get in always, even if I don't know the password.

login or register to post comments

Having simply hash - will

Submitted by reklamabestcom on December 26, 2006 - 11:33.

Having simply hash - will not enter into base, hash it is the coded password though I can and be mistaken.

login or register to post comments

AJAX Login System

Submitted by Tee on January 17, 2007 - 23:01.

Thanks to the article's author, interesting thing! i made also everythink, but it doesnt work GrĂ¼ner Tee Greetings

login or register to post comments

solution that might help

Submitted by monello on February 22, 2007 - 14:26.

I struggled for a couple hours to find the solution for my problem with this login system.
It turns out that the data-type of the "seed" field in the "seeds" table is a timestamp. A time stamp returns a value in the following format: 2007-02-22 14:14:57
The problem with this is that the md5 encryption routine used inside login_controller.js -> hex_md5() does not encrypt the seed/timestamp the same as md5() in the login.php script.
It encrypts the password part into identical hashes but as soon as you add the seed/timestamp, the final hashes you try to compare, differs.

I wrote two replace-regexes that strips the non-numeric characters from the seed and now it works perfectly.

So inside the login_controller.js script, go to the the handleHttpGetSeed() function and replace this line:
seed=results[1];

with these two lines:
var re=/-|\s|\:/g;
seed = results[1].replace(re,"");

Then inside the login.php script, just before this line:

if (md5($user_row['password'] . $seed_row['seed']) == $_GET['hash']) {

add this line:
$seed_row['seed'] = preg_replace("/\-|\s|\:/","",&$seed_row['seed']);

This will modify the seed/timestamp in both scripts from this format: 2007-02-22 14:14:57 to this 20070222141457

login or register to post comments

AJAX VS PHP LOGIN

Submitted by tdd1984 on March 16, 2007 - 02:05.

You know granted I'm not a ajax expert, but when it comes to php I'm pretty good, but what I'm trying to understand is whats the big deal using this type of login system, I mean I read what you say, but why not just use php, and store it with md5() hash. I just find this method a little bit more complexed, and time consuming granted thats probably just me since I'm not too familiar with xmlhttp, which I was going to use it the other day on a project, but didn't. I guess I am going to bookmark this article, and look into it a little bit deeper, but for the best part it was well written, but I'm just so use to using php for login systems, and etc that I don't use anything else anymore. Tyler Dewitt

login or register to post comments

concatenation or variable declaration problem?

Submitted by coleki on March 22, 2007 - 04:17.

first, i have to say i agree that this script still leaves the possibility of a man-in-the-middle attack open, but used in combination with HTTPS and certificate authentication it should be great. that said, i have tried implementing this script and have run into a problem. first off, though, i made a couple of changes to the source: login.php
// now remove the random key that was made for this request
mysql_query('DELETE FROM s WHERE id=' . (int)$_GET['id']);
should be:
// now remove the random key that was made for this request
mysql_query('DELETE FROM seeds WHERE id=' . (int)$_GET['id']);
for added security, i implemented sha1 encryption instead of md5, and to avoid the non-alphanumeric character problem described in another comment, i changed the seeds table, making the seed field a varchar(40) and changed the code in login.php to:
if ($_GET['task']=='getseed') {
//the value of $static doesn't matter, though i intentionally kept it alphanumeric
$static="seli5ynuw3hygpn9etcr";
$hash = sha1(mt_rand().date('YmdHis').$static);
mysql_query('INSERT INTO seeds (seed) VALUES (\''.$hash.'\')');
echo(mysql_insert_id().'|'.$hash);
}
the problem i am experiencing is that these two expressions, which should evaluate to the same hash, do not evaluate to the same value: (in login_controller.js)
hash = SHA1(SHA1(password) + seed);
(in login.php)
//$check is a variable i created that is compared with $_GET['hash'] in the next line.
//keep in mind that $user_row['password'] is not the plaintext password, but a sha1 hash of the password, where as the JS variable 'password' is the plaintext password from the HTML form.
$check = sha1($user_row['password'] . $seed_row['seed']);
The only reason I can think of that these two values would not evaluate the same is that JS and PHP concatenate the variables differently, or that there is some confusion about the variable type, since none of the variables have to be declared as a certain type beforehand. I have already checked to make sure that the JS SHA1() function I found works the same as the built-in php sha1(). One strange thing I have noticed is that in php,
$hash = sha1($password);
and
$hash = sha1(strval($password));
don't evaluate to the same thing, even when $password is alphanumeric.

login or register to post comments

replying to myself

Submitted by coleki on March 22, 2007 - 08:20.

sorry about replying to myself here... my friend found what was causing the error: one of the variables needed to be trimmed. in the validateLogin() function in login_controller.js, the line that hashes the password with the seed should now look like this:
hash = SHA1(SHA1(password) + trim(seed));
once he changed that, it worked! :) p.s. - if anyone else wants to use SHA1, i found the JS function here http://www.webtoolkit.info/javascript-sha1.html

login or register to post comments

UTF-8 encoding

Submitted by Tjeerd Kramer on May 22, 2007 - 05:44.

One of my first experiences with AJAX/XMLHttpRequest resulted in a mindcrushing experience. I just didn't understand why some of my characters weren't displayed properly, until I found out Javascript itself uses UTF-8 encoding, while I was using ISO-8859-15. Changing my files into UTF-8, setting mySQL connection to UTF-8 and adding a line to the .htaccess solved the problem.

.htaccess line:
--------------------------------------------
AddDefaultCharset utf-8

mySQL UFT-8 command in PHP:
--------------------------------------------
mysql_query("SET NAMES 'utf8'");

I was building a rating system where a user can rate certain hotspots in European cities. After entering the amount of stars, parts of the page would auto refresh itself by using a XMLHttpRequest. Take a look at the site here if you are interested in the script. The site is in dutch, but its functionality is pretty straigtforward for anyone to view. Anyone who is interested in how I've done it, feel free to contact me.

login or register to post comments

not enough

Submitted by snfc21 on May 28, 2007 - 18:33.

unfortunately, it lacks the ability to setup sessions or at least to send cookies so that if you navigate on another page you still stay logged in. I'm not sure if the best way to do this is with sessions or with cookies, i'm trying to do this with cookies without too much success so far. I;m trying to do this from login_presentation, setting a cookie first when the user logs (on showlogin()), and afterwards in login.html to check for cookies before displaying again the login menu. What I wanted to use this for was a CMS where when users log in they stay logged in as long as they want (they will be able to set up the cookie expiry time let's say). Anyway, where your login system screwed me was that if i clicked another link , the login box gets displayed again and so you will need to login again.

regards,
dan caescu

login or register to post comments

also some problems in login.php

Submitted by snfc21 on May 28, 2007 - 18:39.

If you come to the point where you see it was not working, you will start looking into the files posted. My changes in order to make it work were:

login.php:
$seed_row['seed'] = preg_replace("/\-|\s|\:/","",&$seed_row['seed']);
$auth_string = md5($user_row['password'].$seed_row['seed']);
if ($auth_string == $_GET['hash']) {
// logged in
echo('true|' . $user_row['name'] . $user_row['surname']);
// now remove the random key that was made for this request
mysql_query('DELETE FROM s WHERE id=' . (int)$_GET['id']);
}


and on login_controller.js i think it stays the same :
hash = hex_md5(hex_md5(password) + seed);
just above the line with //open the http connection

if it is different then the way it looked like, then change it, i am not sure if it was the original way or not.

regards,
dan caescu

login or register to post comments

and now the cookies part

Submitted by snfc21 on May 28, 2007 - 19:02.

my new function handleHttpValidateLogin :

function handleHttpValidateLogin()
{
// did the connection work?
if (http.readyState == NORMAL_STATE) {
// split by the pipe
results = http.responseText.split('|');
if (results[0] == 'true')
{
hasSeed = false;
loggedIn = true;
fullname = results[1];
messages = '';
hidediv('loginfrm');
createCookie('islogged','yes',1);
createCookie('username',username);
}
else
{
messages = results[1];
}
showLogin();
}
}

And my new login.html file (at least the important stuff):
< input type="text" name="username" id="username" < ? php
if ($ _COOKIE["username"]) { echo "value=\"".$ _COOKIE['username']."\"" ; }
if ($ _COOKIE["islogged"] == "yes") { echo " disabled "; }
? > size=10 style="font-size: 11px; font-family: verdana; font-weight: bold;">
< label for="password">Parola: < / label>
< input type="password" name="password" id="password" size=10
< ? php
if ($ _COOKIE["islogged"] == "yes") { echo " disabled "; }
? >
style="font-size: 11px; font-family: verdana; font-weight: bold;">

regards,
dan caescu

login or register to post comments

I have found the trouble. I guess.

Submitted by menks on August 1, 2007 - 17:03.

I'm not an expert but, according to published code, I think there is a mistake:
In the login_controller.js there is a line in the validateLogin():

hash = hex_md5(hex_md5(password) + seed);

and in the login.php the checking is done in this line:

if (md5($user_row['password'] . $seed_row['seed']) == $_GET['hash'])

I mean the md5 module is applied to the (password+seed) set.
I just changed the line in the javascript by this one:

hash = hex_md5(password+seed);

It worked for me.

login or register to post comments

Login

Submitted by ggiotopoulos on October 9, 2007 - 22:01.

So, I noticed that in Firefox when I entered the username and password I had to click outside of the form to login. Where is the login button? Or am I guilty of NOT 'imagin[ing the] wider, richer range of possibilities.'

login or register to post comments

$seed_row = mysql_fetch_assoc($result)

Submitted by robbell85 on October 27, 2007 - 23:56.

I feel like I'm really close to getting this thing working on my site. lol

The problem seems to be that the results from the seed query will not go into $seed_row when the statement "$seed_row = mysql_fetch_assoc($result)" is used.

This is causing it to go straight to the error message "Unknown error (hacking attempt)." after "if (!$seed_row)"

Has anyone come across this? The query must come back with some values from the seed table because it gets passed the "if (!$result)" statement, but fails on the next one (straight after the mysql_fetch_assoc action).

Not sure what to try now, any input would be great, thanks.

P.S. - this is all in the login.php file

login or register to post comments

Slight Bug

Submitted by jbestrom on January 3, 2008 - 22:22.

There is in issue with the creating of seeds.
Say for example you goto a page with this login and click the username box it creates a seed for you. If the user then leaves the page or closes the browser that record in the database does not get deleted.
This can also happen if the user logs out and it defaults back to the username box then they leave the site.

I suppose a person can add checks in their getSeed stuff to clear out the table if a record is a day or so old.
Just thought I would inform everyone of this little detail, so you don't always have to manually clear your seeds table cause the above issues will happen sooner or later.

login or register to post comments

The access keys for this page are: ALT (Control on a Mac) plus:

evolt.orgEvolt.org is an all-volunteer resource for web developers made up of a discussion list, a browser archive, and member-submitted articles. This article is the property of its author, please do not redistribute or use elsewhere without checking with the author.