Skip to main content

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');
            }
[...]

Comments

Popular posts from this blog

Mirth: recover space when mirthdb grows out of control

I was recently asked to recover a mirth instance whose embedded database had grown to fill all available space so this is just a note-to-self kind of post. Btw: the recovery, depending on db size and disk speed, is going to take long. The problem A 1.8 Mirth Connect instance was started, then forgotten (well neglected, actually). The user also forgot to setup pruning so the messages filled the embedded Derby database until it grew to fill all the available space on the disk. The SO is linux. The solution First of all: free some disk space so that the database can be started in embedded mode from the cli. You can also copy the whole mirth install to another server if you cannot free space. Depending on db size you will need a corresponding amount of space: in my case a 5GB db required around 2GB to start, process logs and then store the temp files during shrinking. Then open a shell as the user that mirth runs as (you're not running it as root, are you?) and cd in

From 0 to ZFS replication in 5m with syncoid

The ZFS filesystem has many features that once you try them you can never go back. One of the lesser known is probably the support for replicating a zfs filesystem by sending the changes over the network with zfs send/receive. Technically the filesystem changes don't even need to be sent over a network: you could as well dump them on a removable disk, then receive  from the same removable disk.

How to automatically import a ZFS pool built on top of iSCSI devices with systemd

When using ZFS on top of iSCSI devices one needs to deal with the fact that iSCSI devices usually appear late in the boot process. ZFS on the other hand is loaded early and the iSCSI devices are not present at the time ZFS scans available devices for pools to import. This means that not all ZFS pools might be imported after the system has completed boot, even if the underlying devices are present and functional. A quick and dirty solution would be to run  zpool import <poolname> after boot, either manually or from cron. A better, more elegant solution is instead to hook into systemd events and trigger zpool import as soon as the devices are created.