Monthly Archives: October 2014

Login / logout when using Ajax against Domino

I tried to figure out how to correctly login and logout of Domino from an Ajax service that I call from a mobile app. Well, to be more specific I actually had login working fine – but logout just wouldn’t do it correctly….

The way I saw this was by issuing the following command on the Domino server console:

tell http show users

But now, let’s step a little back and let me explain what I do.

I have created an XPage (service.xsp) that is just the “glue” to bind the requests to a managed bean. It looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false" viewState="nostate">
<xp:this.afterRenderResponse><![CDATA[#{javascript:ServiceController.process()}]]></xp:this.afterRenderResponse>
</xp:view>

I have left out the short description for the programmer to see how to use it – but you should add it… The ServiceController is set up in the faces-config.xml file (as any other managed bean). Now, the “process()” method will find out if the request is a “GET” or “POST” method and handles the request accordingly. For logins I do a POST where I submit the username and password (and some other details about the device). From the bean I start an http connection with the url “/names,nsf?login” and then I add the cookie (DomAuthSessId or LtpaToken) to the response of the call to service.xsp.

This is the source code that does all this (some validation and error handling left out):

String urlParameters = "username=" + userName + "&password=" + password;
String request = Util.getSiteRootUrl() + "/names.nsf?login";
URL url;
try {
   url = new URL(request);
   HttpURLConnection connection = (HttpURLConnection) url.openConnection();
   connection.setDoOutput(true);
   connection.setInstanceFollowRedirects(false);
   connection.setRequestMethod("POST");
   connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
   connection.setRequestProperty("charset", "utf-8");
   connection.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters.getBytes().length));
   connection.setUseCaches(false);
   DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
   wr.writeBytes(urlParameters);
   wr.flush();
   wr.close();
   BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
   StringBuffer sb = new StringBuffer();
   String inputLine;
   while ((inputLine = in.readLine()) != null)
      sb.append(inputLine);
   in.close();
   HttpCookie authCookie = null;
   String cookieString = connection.getHeaderField("Set-Cookie");
   if (null != cookieString) {
      List<HttpCookie> cookies = HttpCookie.parse(cookieString);
      for (HttpCookie cookie : cookies) {
         if (AUTH_COOKIE.equalsIgnoreCase(cookie.getName()) || AUTH_COOKIE_MULTI.equalsIgnoreCase(cookie.getName())) {
            authCookie = cookie;
         }
      }
   }
   connection.disconnect();
   if (null == authCookie) {
      // Find reason code in reply from server:
     String reason = getInputValueFromHtml("reasonType", sb.toString());
      if ("".equals(reason)) {
         reason = "2";
      }
      debug("Not logged in. Reason: " + reason + ", " + getLoginFailReason(reason, userName));
      addErrorMessage(null, getLoginFailReason(reason, userName));
      throwValidationErrors();
   } else {
      // User logged in. Add cookie to request and response and redirect...
      Cookie c = new Cookie(authCookie.getName(), authCookie.getValue()); // Need to convert from HttpCookie to Cookie
      if (StringUtil.isNotEmpty(authCookie.getDomain())) {
         c.setDomain(authCookie.getDomain());
      }
      c.setPath("/");
      ((HttpServletResponse) Util.getExternalContext().getResponse()).addCookie(c);
   }
} catch (IOException e) {
   e.printStackTrace();
}

This actually works fine! You can see that I have a “Util” class that will provide various elements (eg. ExternalContext, request, response, etc.) – but you get the idea from the method names.

The beauty of this approach is that the authorization cookie gets attached to the response and will automatically get added to the next request sent to the service.xsp – thus recognizing the user as logged in.

So what I do is first log the user in for services that require authentication. Then I call the services (a handfull) and then log the user out again. First, I simply removed the authentication cookie from the response. But that was not sufficient. Then I tried to do create an http connection in the same way as the login – just with the url “/names.nsf?logout” – I still had sessions left in the http task. Now with the help from several other XPages specialists I was pointed at this response from Tony McGuckin on StackOverflow. I then tried to translate the code to Java and call that for logout. But it still left http sessions hanging around….

I played around with various trials – and found out that if I called a url with “?logout” directly from my mobile app (Ajax) then it actually logged out and the http session was released – but then it did not call my service… and then I could not call my own code while doing the logout (e.g. logging).

Well, that could be solved by using plain old Domino web url methods, i.e. the “redirectto” action. So if I called a url like this:

//srv.company.com/db.nsf?logout&redirectto=/db.nsf/service.xsp?open%26command=logout

So far so good…. Then I tried out doing it the other way around. I now call the service.xsp (with a GET request) like this:

//srv.company.com/db.nsf/service.xsp?open&command=logout

and then the logout command will call this Java code:

try {
   String logoutUrl = Util.getSiteRootUrl() + "names.nsf?logout";
   logoutUrl = logoutUrl + "&redirectto=" + URLEncoder.encode(Util.getBaseUrl() + "service.xsp?open&command=loggedOut", "utf-8");
   Util.getExternalContext().redirect(logoutUrl);
} catch (IOException e) {
   e.printStackTrace();
}

… and then control is returned to the service controller (though with a new “loggedOut” command) that will return the right JSON to the mobile app calling the service in the first place.

This works like a charm…!

Conclusion: Do not try to solve the logout by just removing cookies. You may succeed in logging out the user (well, you will) – but you will have left the http session hanging around until it times out.

Tuning your XPages…

Ok this may promise a little more than I can hold. However, I just wanted to point the attention to some good advice that I had from Tony McGuckin (co-author of Mastering XPages) at IBM Connect 2014 in Orlando. This piece of advice surfaced through a review of a large application that I was building… Læs mere