Coding

Developing a complete client-server application with qooxdoo and NodeJS on Cloud9IDE. Part 3: Authentication

In my last post, I announced that today’s episode will deal with authentication (user management) and authorization (rights management). That turned out to be a bit too much, so I’ll restrict myself to authentication. We’ll also need some sort of data storage in which to save user data, so we’ll need to set this up first.

To make things easier to follow, I have put the complete code of the application on GitHub, so you can browse and have a look at the code if you get confused.

An extensible key-value store

For the purposes of this tutorial and the ones to follow, we don’t need any powerful database. A simple key-value store, stored in-memory is fully enough for our needs. At the same time, we want to be able to exchange this database with a powerful one later without having to change a lot of code.

Here, again, the virtues of Architect come into play. We’ll create a plugin with a minimalistic API that can be extended and wired to a “real” database once it is needed. All we need for the moment is a getter and a setter method. Any database backend that we might end up choosing will work asynchronous, so the API will need to reflect this. That is why we also need a library to deal with asynchronous function calls. There are many to choose from; we’ll take async.js (npm install async) since it seems quite comprehensive and is used in a lot of other projects.

You’ll already know by now where to put the following code:

configs/build.js & configs/source.js

...
  { packagePath: "../plugins/store" },
  { packagePath: "../plugins/users" }
...

plugins/store/package.json

{
    "name": "store",
    "version": "0.0.1",
    "main": "store.js",
    "private": true,
    "plugin": {
        "provides": ["store"]
    }
}

plugins/store/store.js

// This plugin provides a very simple in-memory key-value store
// with an asynchronous API
module.exports = function setup(options, imports, register)
{
  // the Store object
  function Store()
  {
    // the data
    var data = {};

    // the number of key-value records
    var length = 0;

    // the exported API: get, set, length
    return {
      get : function( id, callback )
      {
        callback( null, data[id] );
      },
      set : function( id, value, callback )
      {
        if ( typeof data[id] === "undefined" ) length++;
        data[id] = value;
        callback( null );
      },
      length : function( callback )
      {
        callback( null, length );
      }
    };
  }

  // register plugin
  register(null, {
    store: {
      createStore: function() {
        return new Store();
      }
    }
  });
}

The user management plugin

plugins/users/package.json

{
    "name": "users",
    "version": "0.0.1",
    "main": "users.js",
    "private": true,
    "plugin": {
        "consumes": ["store","socket"],
        "provides": ["users"]
    }
}

plugins/users/users.js

// This plugin provides user authentication
module.exports = function setup(options, imports, register)
{
    // create new store for users
    var userstore = imports.store.createStore();

    // create some sample user data, this will be removed later
    // we could of course keep the data in a simple array
    var async = require('async');
    var userdata = [
      { id: "john", name : "John Doe", password : "john" },
      { id: "mary", name : "Mary Poppins", password : "mary" },
      { id: "harry", name : "Harry Potter", password : "harry" }
    ];
    async.forEach(userdata,
      // iterator
      function(item, callback){
        userstore.set( item.id, item, callback);
      },
      // final callback
      function(){
        userstore.length(function(err,length){
          console.log("Store now has %s entries.", length);
        });
      }
    );

    // API
    var api = {
      // very simple authentication
      authenticate : function(userid, password, callback){
        userstore.get(userid, function( err, data ){
          // user does not exist
          // you wouldn't usually reveal this
          if( ! data )
          {
            return callback("Unknown user");
          }
          // check password
          if ( data.password == password )
          {
            return callback( null, data.name );
          }
          // authentication failed
          return callback( "Invalid Password" );
        });
      }
    };

    // Listen for authenticate event and return result of authentication to browser
    var io = imports.socket;
    io.on("connection", function(socket){
      socket.on("authenticate",function(data, callback){
        api.authenticate(data.username, data.password, callback);
      });
    });

    // register plugin and provide plugin API
    register(null,{
        users : api
    });
}

If you run the server.js, you should have a console message “Store now has 3 entries.”. So we’re good. We’ll use this mock data for username-password authentication. Stop the server, we’ll restart it later.

Creating a login widget on the client

On the client, we need a login widget. In order to save us some work, we can use the one included in the “Dialog” contribution, which also gives us other useful dialog widgets. At the moment, qooxdoo contributions (contribs) are hosted on sourceforge and can be included automatically by the generator using special syntax.  In preparation of a new system, which will allow to maintain contrib code outside of sourceforge, the code of the Dialog contrib has been moved to GitHub and can be pulled (or downloaded) from there. Create a new folder “qooxdoo-contrib” in the top level directory and get the code with git:

mkdir qooxdoo-contrib
git submodule add https://github.com/cboulanger/qx-contrib-Dialog qooxdoo-contrib/Dialog

Then, tell the generator to include the contrib code by adding a library section to /testapp/config.json:

  "jobs": {

    "libraries": {
      "library": [{
        "manifest": "../qooxdoo-contrib/Dialog/Manifest.json"
      }]
    },
...

Now, replace the complete “main” function code in testapp/source/class/testapp/Application.js with this:

    main : function()
    {
      // Call super class
      this.base(arguments);

      // Enable logging in debug variant
      if (qx.core.Environment.get("qx.debug"))
      {
        // support native logging capabilities, e.g. Firebug for Firefox
        qx.log.appender.Native;
        // support additional cross-browser console. Press F7 to toggle visibility
        qx.log.appender.Console;
      }

      // set up socket.io
      var loc = document.location;
      var url = loc.protocol + "//" + loc.host;
      var socket = io.connect(url + "/testapp");

      // Create a button
      var loginButton = new qx.ui.form.Button("Login", "testapp/test.png");
      var doc = this.getRoot();
      doc.add(loginButton, {left: 100, top: 50});

      // Add an event listener for the button
      var loginWindow, loginStatus = false;
      loginButton.addListener("execute", function(e)
      {
        // if someone is logged in, log out
        if (loginStatus){
          loginButton.setLabel("Login");
          loginStatus = false;
          return;
        }

        // create or reuse login window
        if ( ! loginWindow ){
          loginWindow = new dialog.Login({
            image : "dialog/logo.gif",
            text  : "Please log in",
            checkCredentials  : checkCredentials,
            callback : finalCallback
          });
        }
        loginWindow.show();
      },this);

      // this asyncronously checks the user credentials
      function checkCredentials( username, password, callback ) {
        socket.emit("authenticate", { username:username, password:password }, callback );
      }

      // this reacts on the result of the authentication
      function finalCallback(err, data){
        // error
        if (err) {
          return dialog.Dialog.error( err );
        }
        // Success!
        loginStatus = true;
        loginButton.setLabel( "Logout " + data );
        dialog.Dialog.alert("Welcome, " + data + "!" )
      }
    }

Now run the generator:

cd testapp
python ./generate.py --no-progress-indicator source

Now run server.js and open the source version of the application from http://x.y.c9.io/testapp/source/index.html. When the app has finished loading, you should see a “Login” button. After you press it, the login widget should appear and you can log in with john/john or mary/mary. The killer feature of this immensely useful application is that you can even log out again!

Ok, this doesn’t look like very much yet, but we’ve put in place a basic element of an application that can gradually be improved by adding more sophisticated functionality. One could, for example, add authentication through third-party authentication providers like Google, Facebook or Mozilla by using node libraries such as Passport or everyauth. We need to continue though, with another very important element of an application: access control or authorization. This will be the topic of the next post.

Again, if you want to see the complete source code, head over to  GitHub. Happy coding!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s