Thursday, October 13, 2011

RIP Dennis Ritchie

So long and thanks for all the segfaults!Dennis Ritchie died last week. While Steve Jobs had a great influence in my past years (I have only recently become an Apple customer/fan) my first experiences with Ritchie's (and Kernigan) go back  to my university days and are forever linked to my first baby steps with Slackware linux, fvwm and tcp-ip. I miss those days as anyone misses his/hers youth.

So long Dennis and thanks for all the segfaults!

Monday, October 03, 2011

Grails ajax login from a Sproutcore app

Warning: this post is a work in progress. Full source code for both Sproutcore and Grails parts can be found at my github account https://github.com/unicolet.

All the Sproutcore examples that I've found so far only show how to fetch data from different backends, but there is unfortunately very little material on authentication.
In my case I'm hooking up to a Java backend written in Grails with authentication being provided by by Spring Security.

The grails spring-security-core plugin documentation already incorporates Ajax logins so the server-side code is already in place. We only have to apply one small tweak to make it work with Sproutcore.

Prerequisites: Grails, Sproutcore > 1.6 with Statecharts.

The statechart in the figure illustrates the states and transitions we plan to handle.

Authentication

Let's start with authenticating the user. We will deal with reconnecting the session later.

Assuming you have a basic Grails app with spring-security-core set up, on the Grails side the modifications boil down to editing the LoginController.ajaxSuccess closure as follows:

/**
 * The Ajax success redirect url.
 */
 def ajaxSuccess = {
   response.addCookie(new Cookie("JSESSIONID",session.getId()))
   render([success: true, guid: springSecurityService.authentication.name, username: springSecurityService.authentication.name] as JSON)
}


The truly important change that we made is actually sending back the session cookie. This is critical because in a normal Grails/Java app your browser will already have the session cookie so the LoginController does not bother with sending it again. But in a sproutcore app were interaction might start with the login process itself it is instead critical to send the cookie back so that subsequent requests can be mapped to the appropriate session.
Also note that I am returning the username as the guid. I could have returned a database id but since not all authentication providers have an id property (LDAP or ja-sig CAS come to mind as noteworthy examples) I opted for reusing the username (lowest common denominator).

The Sproutcore side instead consists in making the right kind of request (note the 'application/x-www-form-urlencoded' content-type header):

SC.Request.postUrl('/yourapp/j_spring_security_check',
    $.param({j_username:'user',j_password:'secret'}))
    .header('Content-Type', 'application/x-www-form-urlencoded')
    .notify(this, 'didAuthenticate', null, null)
    .send();


The response will be handled by a function like the following:

didAuthenticate : function(response, store, query) {
  if (SC.ok(response)) {
    var r=null;
    if(!response.isJSON())
      // by default the response should not be json, so let's parse it
      r=SC.$.parseJSON(response.get('body'));
    else
      r=response.get('body');
    if(r.success) {
      // login successful
      Maps.statechart.sendEvent('loginSuccessful', {id:r.guid});
     } else {
      // login failed
      Maps.statechart.sendEvent('loginFailed', r.error);
    }
  }
},


This couple of code blocks, that I placed on a Controller, is at the core of the notLoggedIn to loggedIn transition in the above state chart. When the user has been authenticated the app switches into the loggedIn state and only then it issues a retrieve request to the store for the user details (name, roles, email, etc).
For the details on how the transition is made read the Recap section at the end of this post.

Checking for existing session (remember me)

What is more interesting instead is the checkingSession state at the bottom. If a user reopens the application and the server side session is still active then the app should not ask the user to authenticate himself again and transition into the loggedIn state automatically.

We implement session checking by adding a very simple method server side:

/**
 * Return user info
*/
def userInfo = {
   if (isLoggedIn()) {
      render([success: true, guid: springSecurityService.authentication.name, username: springSecurityService.authentication.name, authenticated:true] as JSON)
   } else {
      render([error: "Not authenticated"] as JSON)
   }
}


After that we add a Sproutcore model, datasource and query to handle the retrieve the details of the currently logged in user from the userInfo Grails method above. This is pretty standard code so I'm not covering it here (it's all available here anyway).
It is instead more interesting to enter into the details of how it is used.

This is the code for the checkingSession state:

    checkingSession: SC.State.extend({
        enterState: function() {
            // try to load user data from existing server session
            Maps.authenticationManager.set("content",Maps.store.find(Maps.User, Math.random()));
        },

        exitState: function() {
            // do nothing
        },

        noLoginSession: function() {
            this.gotoState("notLoggedIn");
        },

        userLoaded: function() {
            this.gotoState('loggedIn');
        }


When the app is loaded it goes directly into the checkingSession state and on entering it basically asks the remote server if the user has an existing session server-side.
AuthenticationManager is just an object controller whose content is set to the user's SC.Record when the session is present or to an anonymous SC.Record when it is not. Now we just need to add an observer to AuthenticationManager.content that will trigger the state change when the response has been processed. To keep things simple we add the observer to the AuthenticationManager itself:

    whenUserLoaded: function() {
        var content=this.get("content");
        if( content && content.get("status")==SC.Record.READY_CLEAN) {
            // the User model has an authenticated attribute
            // even when the user is not logged in the controller does
            // have a default user object whose username is anonymous
            if(this.getPath("content.authenticated")) {
                Maps.statechart.sendEvent('userLoaded');
            } else {
                // anonymous, aka not logged in
                Maps.statechart.sendEvent('noLoginSession');
            }
        }
    }.observes("content"),


When the controller is notified of a change it checks if the user has authenticated and if he has it sends a userLoaded event to the stateChart. The userLoaded event handler moves the app in the loggedIn state (as shown above). The userLoaded event handler is defined for both the checkingSession and notLoggedIn states.

Recap

User lands on our app. The app goes into the sessionChecking state and triggers an http request for /login/userInfo. The request returns an anonymous user and triggers the observer on the authenticationManager.content. The observer sees that the user has not been authenticated and sends the noLoginSession event to the state chart. The application then enters the notLoggedIn state and prompts the user to identify himself.

When he clicks the login button or whatever other mean the app has to identify him the first two functions (which I have placed in the AuthenticationManager, btw) come into play and check his credentials by post-ing them to /j_spring_security_check.

Depending on the outcome the loginSuccessful or loginFailed event are sent to the statechart. The latter does nothing special except perhaps informing the user. The former instead uses the same trick user by the sessionChecking state to trigger the transition to the loggedIn state:

[...]
            loginSuccessful: function(user) {
                Maps.authenticationManager.set('content', Maps.featuresStore.find(Maps.User, user.id));
            },

            userLoaded: function() {
                this.gotoState('loggedIn');
            }
[...]