How To: Login to Domino Using Ajax
Back in January I showed how you can add a "flyout" login box to any page of your Domino application. It's a nice easy way for users to login at any point they wish to. The one problem with it is that if, for whatever reason, authentication fails, you are then redirected to the normal login form -- be it the ugly default one or a custom version from domcfg.nsf.
Whichever login form the user ends up seeing and no matter if it's ugly or not the chances are you didn't ever want the user to leave the application itself in the first place. Particularly if the interface happens to be Ext-based.
My latest task has been to implement a login interface for just such an Ext-based Domino application. In order to avoid the user seeing anything other than a pretty-looking Ext app I decided to use Ajax requests to log the user in. It turns out that what I thought would be a relatively simple exercise took quite a while. What I learnt is worth sharing and I'll end with a demo you can view/copy.
How Does It Work
Let's start where the last "how to" left off and assume we have a bog standard HTML Form on the page which POSTs the entered user name and password to the URL /names.nsf?Login. No JavaScript is involved thus far.
Now, using this same form, we can now add an onclick event to the Form's login/submit button. Whereas before the button looked like this:
<input type="submit" value="Login" />
It would end up looking like this:
<input type="submit" onclick="DEXT.Session.Authenticate(this.form); return false;" value="Login" />
Notice the "return false" statement in the onclick event. This prevents the browser submitting the form in the usual way. We'll be processing the form in the JavaScript "Authenticate" method instead. Note that this way of doing it means it's still accessible as the form will still work with JavaScript disabled.
The DEXT.Session.Authenticate method looks like this, which you'll no doubt recognise as Prototype-based JavaScript (some bits removed for the sake of brevity).
DEXT.Session.Authenticate = function(frm){ frm.RedirectTo.value = DEXT.Database.Path+"$icon"; //Load something small! $('login-message').update("Authenticating with the server..."); $('login-form').request({ onSuccess: function(){ if( document.cookie.match(/DomAuthSessId|LtpaToken/)){
$('login-message').update("Authenticated. Reloading page..."); location.reload(); }else{ $('login-message').update('Authentication failed. Please try again.'); } } }); }
The Form.request() method used is simply shorthand for the Ajax.request() method. It saves us having to tell it what URL, method and data to use as it can get all that from the form itself. The other JavaScript libraries all have similar methods available and porting this code to them should be a doddle.
The key to making it all work is simply looking for the existence of a certain cookie once the response is received from the server. If there's a cookie called DomAuthSessId or LtpaToken (for SSO setups) attached to the document then we know the authentication was a success. At this point we reload the whole page. Whether you need to do that depends on how much of the page layout changes once logged-in. If not much changes then you can probably skip this step and have it a truly Ajax-only login process.
An Example
You can see this method in use now in the DEXT app. Hover over the Login link top right (not in IE6 though, yet) and the login form will appear. It should be pre-populated with a valid login. If you press login you'll see a message about it contacting the server and then the page reloading with the name "Dext User" top right and the Login link no longer there.
Now logout using the logout link which replaced the login one from the previous step. When the Login link appears again bring up the login form but alter one of the field values. Try and login now and you should see an error message, while the form remains in place, allowing you to try again.
What Use Is It?
As mentioned this method still reloads the whole page when a login is successful, so why use Ajax, as this, in effect, adds another round-trip to the server that isn't part of a normal non-Ajax login. Well, put simply, it's for the sake of the user. Rather than redirect them to a different interface and out of the application they were in it allows repeated attempts at logging in all from one place.
Lessons I Learnt Along The Way
At first I'd thought I'd be able to examine the page returned to the Ajax request directly and look at its headers. When you login to Domino by POSTing the data the server replies with a HTTP code of 302, which tell the browsers (or Ajax code in this case) where to go next. You're either redirected to the root ("/") of the server or to a URL you specify by passing a value in a field called RedirectTo.
The headers for the immediate response from the server look like this:
HTTP/1.1 302 Found Server: Lotus-Domino Date: Tue, 07 Oct 2008 09:31:20 GMT Connection: close Location: http://dover/codestore/apps/dext.nsf/$icon Set-Cookie: DomAuthSessId=E73F7A4E35C7A4A9D2CBA8CA3628F067; path=/
My thought was that I'd be able to examine the headers to this page and if the Set-Cookie header existed and had a value of DomAuthSessId or LtpaToken then hey presto, we're in.
However, from what I've read, it looks like Ajax doesn't let you examine the 302 redirect. Instead it waits until it's fetched the page to which it's redirected and then it lets you process that as the response. Luckily at this point the Set-Cookie header has been processed by the browser and we can inspect the document.cookie from our JavaScript code.
Another lesson I learnt is that you can't POST data to a URL that ends in ?open&login. You can only use a URL ending like that with GET requests. The only time you can POST directly to a login form is if you point the form directly at /names.nsf?login.
I implemeted an AJAX login for my app having read your last article...so thanks!
Your method of checking the cookie is much more elegant than mine.
My AJAX login POSTs a login request, onSuccess I then POST a ?CreateDocument, which does a simple XML print back, and if the value is ON, we're logged in, if not, we're not! But I was very unhappy with this extra ?CreateDocument.
My forms rely heavily on AJAX, so if the session gets dropped, the form does not work correctly, but the user may not notice that specific fields, etc. are not loading correctly, I currently do something that has a completely unnecessary overhead, I check every x secs whether you are connected...his has issues, which I won't go into here. If the session has been dropped, my modal AJAX login form pops up. But there is of course, a much more elegant solution:
Link
Incorporating your cookie technique and the ajax cache, I think will take care of these issues very nicely.
Good work Jake.
A hidden iframe should give you access to the cookie.. Attach an onload-listener to the iframe, and you should be ready to go.
From my experience, it's simplest to create an iframe using a container-div created by dom, and inserting the iframe using innerHTML. Cross browser event-binding on dom-inserted iframes has given me so much head-ache, I've more or less given up on it.
Example:
var ifrContainer = document.createElement( 'div' );
ifrContainer.innerHTML = '<iframe src="/names.nsf?login" onload="someFunction()"></iframe>';
document.body.appendChild( ifrContainer );
function someFunction(){ alert( frames[frames.length-1].document.cookie )}
I already have access to the cookie Tommy. Not sure why an iframe would help?
Argh! I misread the last part.. I thought you meant that the Ajax-request stopped at the 302.. Sorry..
I guess the reason you're reloading the page after the login is to update the user-interface (hide-whens/computed texts)?
Yes Tommy, that's why it reloads the page (that parts optional).
I discussed that in the article too. Be sure to read more thoroughly next time ;o)
Very nice Jake! Ext.nd users have been asking for us to update the old login code you provided for Ext.nd when it was based on ExtJS 1.x. It's been on my todo list and planned for our next release. Now I got to make sure I clear my head and do it my way otherwise someone may accuse us of stealing your code. Hey, you can make it easy on me if you just re-joined Ext.nd. :)
If I had more time I'd be happy to help out on ext.nd Jack. I don't have enough time though. Sorry.
Feel free to use this or any of my code in Ext.nd. It's all "open source" so you can do what you like with it.
In our SSO environment, I ran a test, if I "?logout", the cookie disappears. But if I leave my page idle until the server session expires, the cookie is still available. So if I need to check if I have an active session I still need to check for a response from the server.
Fantastic post, Jake!
Now I can put the user name and password into another cookie and log a visitor into the site when they return. This is a nice feature on many web sites that have a check box that say's "Remember Me". I've been wanting to have the option to add that feature for a long time.
Reminds me of the solution I came up with (http://mdm-adph.blogspot.com/2008/09/domino-serverextjs-timeout-script.html). I just use the normal Domino login initially, though, but anytime there's a timeout (or a user needs higher access), my script hooks them up. (It's tied into the basic Ext.Ajax object so that it fires automatically.)
I must admit, though -- checking for the presence of a cookie is a bit more elegant than the way I check! :D
Rob. Be very wary of doing that! Big can of worms. Most websites that have "remember me" options normally do this by storing the password in MD5 format (if at all). If you store it as plain text and the user isn't aware of this then be prepared for a backlash once they do realise. I know I'd be none to happy if I were the unsuspecting user.
I'd change the onclick event to an onsubmit event for the form.
<form onsubmit="DEXT.Session.Authenticate(this.form); return false;"...
@Nick Wall, You can run a "keep alive" function every five or so minutes that retrieves a page to keep the session alive. The keep alive page should return the logged in user. If the user is "Anonymous", then re-login the user. The page should not require authentication so that it can return Anonymous if the user is no longer logged in.
Good point Tanny. Don't know why I didn't do that in the first place. Will change it shortly.
@Jake (MD5 hash)
I've given this some thought and done some Internet research.
ASSUMPTIONS
Please check my assumptions here.
1. In order to log a person into Domino they must submit a user name and password. Therefor, in order for the "remember me" function to work, the user name and password must be entered into the login form somehow. (Other systems may have other options but, as Domino users, we do not.)
2. An MD5 (or any other) hash is a one-way operation. That is, once the password is hashed it can not be recovered.
3. The attack we are protecting is cookie theft. That is the attacker has obtained our user's "remember me" cookie and is trying to use it to log into the site.
THEREFOR
Given #1 above, whatever we do we must somehow use the "remember me" cookie to retrieve or extract the user name and password to be used for the automatic log in process. All the methods I read about seem to be about obfuscating at least the password and ameliorating the damage a stolen cookie can do.
For example:
Link
DISCUSSION
If I store a unique identifier in the "remember me" cookie (which could be generated by MD5 or many other methods) which lets me look up and pass back the user name and password then that could be used in the log in form. But any attacker that steals that cookie could do exactly the same thing so nothing of real value has been accomplished. All we've done is obfuscated the user name and password.
So given assumption #1 and #3, I see no way to prevent the attacker from obtaining the user name and password. If we could log a user in on the server side then we could protect the user name and password but not prevent the attacker from logging in.
Even public key cryptography fails in this case because there is no safe place to save the user's private key.
CONCLUSION
"Remember me" cookies are not safe and there is no way to make them safe.
Please point out where my reasoning is wrong because I'd like to use this feature. (I did read several places where people recommended saving the user name and password in a cookie.)
Peace,
Rob:-]
Hi Rob. You've done your homework then ;o)
Security aside (I'd need to delve deeper to fully understand it myself) there's the practical side of implementing it, which I can't see being straight-forward.
A typical remember-me feature just knows who you are when you first request a page on the site. Even if we invent a remember-me cookie for Domino we won't be able to use it in that way. Domino's authentication methods are fairly closed off to us.
What you'd have to do is have the page open and then have some JavaScript check for the cookie. If found the same script needs to POST the name/pass values to the server so it can initiate a session and return a valid DomAuthSessId cookie. At this point you're probably going to want to reload the page to make it user-specific. Even if it's really quick it might seem odd and confusing to the user. Maybe it could be done transparently before any content appears. Either way I don't think it's what a user would expect to happen.
As with most things Domino, there's a way to do just about anything. In this scenario though I think I'd suggest a user might use the built-in login-remembering features of the current browsers.
Jake
Thanks for the quick response. After doing all this research, the one comment that made sense is "forget about it!"
He made the point that all browsers will remember your user name and password for you. So all you have to do is click the login button or link.
Does that work with your fly-out log in form?
Rob:-]
Yes, browsers will auto-fill my form (it's just a normal form like any other).
I had a customer ask me about a "remember me" tickbox for their site once. Luckily I managed to convince them it was a security issue and that, should a user want it to, most browsers do it for them now. Thankfully they agreed and didn't push me on it, as I hate having to explain that "You can't do that with Domino" over and over.
How To Logout to Domino Using Ajax
i want to create a fly out pane for my website as rediff but i am unable to create it can anyone help me . If yes please do mail me nktpl@yahoo.co.in quickly .