Skip Navigation

Taking Ajax Logins A Little Further

Getting back to Wednesday's topic of logging in via Ajax I decided to see if I could take it beyond the basics.

The basics being that we use Ajax to POST login details to /names.nsf?login and wait to see if there's a Domino session cookie attached to the response.

The trouble with this basic approach is that it only caters for authentication and not authorisation. What if the login details supplied are ok, but they're not allowed access to the page being requested in the RedirectTo field? With the code I showed you the other day it would assume all was ok as a DomAuthSessId cookie is returned and it would simply reload the page. At this point you'd see a standard login form with a message explaining that you're not allowed in.

How Can We Cater for Authorisation Issues?

Even if there's a session cookie returned we still need to examine the actual page itself to check it's not another login form. I've seen various ways of doing this, which all seem to rely on finding certain text (such as "You are not authorized") within the HTML of the page itself. 

Now, you know me, I'm just not going to be happy with that solution am I. I wanted something more universal/stable, so I looked in to using HTTP headers to convey the actual state of the login request.

What I came up with is a header I've called Domino-Auth-Response. You can see an example of it here in the headers received from Domino following an Ajax login request:

login1

Foolishly I'd tried to login to as a user (called One Test) with no access to the database (Anonymous = Author. Default = None. Changed for purposes of this demo). Instead of reloading the page, which, in effect, locks the user out I can trap the above scenario by inspecting the headers of the returned page, using a line like this in the "onSuccess" function for the Ajax call:

if (transport.getResponseHeader("Domino-Auth-Response")){ //problem

No need to try and search the page. If the above header is found we can handle it in a much more graceful manner, which I'll discuss in a mo. First, about the header.

How To Add a Custom Header to Login Forms

Let's assume you have a domcfg.nsf on your server and you have the right to make changes to the $$LoginUserForm within it.  Open the form and it will look like this by default:

login2

Beautifully designed.

Look inside the Computed Value I've pointed to and you'll see a formula like this:

login3

Although I removed some bits, you get the idea? Depending on the value of the Domino-set field called reasonType a message is shown to the user. This is where you'd be able to internationalise the server if needs be.

Now let's look at the code again and see how we can use it add our new header:

login4

As simple as that! Whatever message is shown to the user we can find this out without having to look inside the HTML. We also know the "reason type" code. This in itself could replace the need for returning the text of the message. On its own the reason code is useful for processing the login in our JavaScript.

Dealing With Unauthorised Logins

If the response is to say "you're not allowed to do that" then it's a fair bet that the user doesn't want to remain logged in as that user if they don't have access. We could log them out simply by over-writing the cookie we just received. Either we log the user out completely by setting its value to "" or (if they were already logged in) we can keep them logged in as the previous user by remembering the old value of the cookie and resetting it to that.

You can give this a go from the DEXT homepage. First login as Dext User (password=dext) . Once logged in notice there's now a "re-login" link up there. Use the login form to re-authenticate as One Test (username and password are both test1).

One Test has no access to the application so you should see a message to that effect. Now refresh the page. Notice you're still logged in as Dext User!

We can do this because the DEXT JavaScript object records all the cookie values when the page loads. We can look to it for the previous session code and reset the cookie to that.

A much more sensible approach. No?

In Summary

This whole exercise has been as much of a proof of concept as anything else. How much use it would be in a real world application I don't know. What it doesn't do is trap any request from the server for the user to login. It only works when the user chooses to login.

The only way we could create a system that relied on it would be to have the onclick of any link/button check the current user's access before opening a form or editing a document. If the current user has access then just carry on. If not then we can display our Ajax login form and set its RedirectTo field to the href value of the link clicked.

Still, whether or not is has a true practical use, it's a good example of using the @SetHTTPHeader function if nothing else and it's been fun playing. Hopefully it's of use to somebody.

Comment Icon 1 Comment Read - Add | Fri 10 Oct 2008 | Open »

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.

Comment Icon 17 Comments Read - Add | Wed 8 Oct 2008 | Open »

The Howlett Family Photo Shoot

For ages my neighbour-cum-friend Wayne had been hassling me about paying a visit to his city-centre photography studio to work out why "the downstairs computer can't see the upstairs one". Because it involved wireless connections, which are always way more hassle than they ought to be, I had been putting it off with a "Yeah, I'll try and find time to pop in at some point".

Then a couple of weeks ago I offered to come in and sort it out on the proviso that we get a free photo session with the kids while I work on the connection.

Here's a rogues gallery selection of the results, starting with the whole Howlett clan (including Quinn, who's an honourary Howlett).

family2

If there's such a thing as being too cute then Felix is having a go at it here. I want to laugh and cry both at the same time when I look at this photo.

family1

Me girls. I've chosen the photos above and below to have framed for hanging in my office.

family3

Here's a close up of Minnie (AKA "Felix in a dress"):

family4

Another of my cheeky little chappy:

family5

After Wayne had reeled off a couple of hundred shots I came back in to the room and he asked if I'd sorted the router. "Yeah, I just unplugged it and plugged in back in again" I replied. I kid you not. Sometimes, knowing about computers really can pay off.

Comment Icon 9 Comments Read - Add | Mon 6 Oct 2008 | Open »

Bug With UntilKey URL Parameter

Almost a year ago I talked about how you can get Domino's UntilKey URL parameter to make its StartKey counterpart do what you'd expect it to -- return only exact matches from the first column in the view. If you had a sorted view of animals you could return all those who were Cats use a URL like this:

animals?ReadViewEntries&StartKey=Cat&UntilKey=Cat_

All's well and good and I've used the the trick repeatedly since, not knowing how I managed without it.

Then this week I was talking about using lists in the first column of a view to allow flexible lookups. Again, all was good. Until I tried to search for the last document in the view. In the example it's a car called Zonda. Type in "Z" and nothing comes back.

The bug: It looks like the UntilKey trick doesn't work on views sorted by a multi-value column. It just won't return the last document.

Taking the example of the cars here's an empty result set for "Z" and here's what you'd expect to see, which points to a view with a normal single value column.

Just another reason not to trust Domino to give you want you want and to go you own way. For me I'll be using my Ajax NotesViewNavigator Object. No doubt there's a performance hit, but at least it does what you want it to.

Comment Icon 3 Comments Read - Add | Fri 3 Oct 2008 | Open »

Simple, Yet Effective Tip - Type-Ahead Lookups Based on Any Word

This is one of my favourite type of tips; as it's simple to do, applicable to most of you and has great benefits for the end user.

typeahead1 Most of us have probably used some sort of type-ahead-drop-down Ajaxey-document-picker at one time or another. Maybe even you're using them in all your applications. 

What values do yours lookup? Maybe it's the name of other people. Maybe it's the names of products. Maybe it's place names. Whatever the user is (in effect) searching for, chances are you can make it a little easier for them by allowing them to match their search in a more flexible way.

For example, let's say you're looking for a car on an insurance website (poor example, but stick with me).

You'd maybe start by typing "For" in the lookup field. What if you typed "Must" though? Chances are it wouldn't work. Why not though?

In the example picture above you can see I've typed in "For" and as well as a couple of Fords it's showing me a Fiat called a "Forentino" (I made that name up), which could prove handy if you're looking for that car and had forgotten it was a Fiat. Either way it's nice to be able to do both.

How have I done this?

It couldn't be easier. All I did was modify the first column in the view that the Ajax looks to for matches. The formula for the column now explodes the field with the car's name in to a list of items. After making the column show lists as separate view entries I can search on any part of the name. 

Here's the view the Ajax looks to:

typeahead2

Notice I've included the unexploded Name value in the list as well so that you still get a match when you type something like "Ford Mu".

Other Real World Uses

A car picker probably isn't the best example of its use but it's something we can all relate to. Any of you remember I used the same example in the first article I ever wrote on this site over eight years ago (Codestore's birthday is on the 9th September but it passed me by this year).

As a more real world practical use imagine how it could be used in the humble name picker. If you have a field where a user selects another user's name what do you have them type first? Should it be the other user's first name or last name? Whichever it is how do you expect the user who is searching to know that? Well, why not make it search first and last names? The formula for the first column in your "Users" view just needs to be:

(FirstName + " " + Surname):(Surname + ", " + FirstName)

Taking it even further you could add more items to the "searchable" list, such as:

(FirstName + " " + Surname) : (Surname + ", " + FirstName) : 
  EmailAddress : MaidenName : NickName

Note that it's the second column in the view that is returned to be listed by the picker. No matter where in the name you find a match the displayed name is always in the same format, but the part with the match is highlighted to help you see why it's in the list.

Hope you find it useful. It's part of the DEXT app and the demo is live now. Download to follow. Although all the code is in the source. The picker (of which there are hundreds of variants) you see in the demo is a Dominofied version of this one.

Comment Icon 13 Comments Read - Add | Wed 1 Oct 2008 | Open »

More blog entries are available in the archive »