Running Spiffy UI with Node.js

by Bess Siegal on June 16, 2011

Fork me on GitHub

Today Bess Siegal drops by so we can show how Spiffy UI works with Node.js.

The best feature of REST is running it from anywhere and calling it from anywhere. Spiffy UI is a GWT framework you can run on any server. We’ve said that from the beginning, now we’re going to prove it.

This article turns the normal stack upside down and creates a rich GWT client that calls a JavaScript server written with Node.js. This simple server and rich client scales to the cloud and shows you a little about Spiffy UI security.

Creating Hello Spiffy Auth Node

Creating a new Spiffy UI project for Java is easy. The project creator gives you a simple build with a GWT client and a Java server. They’re both Java, but the separation between the client and server make it easy to replace the server with a different technology like Node.js.

Node.js runs JavaScript on your server; a little funny since we’re replacing the client-side JavaScript with GWT. The key to this architecture is making a thicker client with a thinner server. When servers get thinner they scale better and take fewer resources.

Spiffy UI already has Hello Spiffy Ant and Hello Spiffy Maven. We’ll make this new project Hello Spiffy Auth Node.

This project builds with Apache Ant. That tool creates the GWT compiled code in a directory called targetDir. Next we create our Node script, HelloSpiffyAuthNode.js. Before we create our server, we call init.

HelloSpiffyAuthNode.js, starting at line 26

init: function() {
    var targetDirFull = path.join(process.cwd(), targetDir);
    path.exists(targetDirFull, function(exists) { 1
        if (exists) {
            HelloSpiffyAuthNode.runServer();
        } else {
            sys.puts('===================================================\n');
            sys.puts('You have to build the Spiffy UI client before \n');
            sys.puts('running the server.  Run the ant in the current \n');
            sys.puts('directory and then run the server again.\n');
            sys.puts('===================================================\n');
        }
    });
},

We run HelloSpiffyAuthNode.js from the project’s root directory so that joining the current working directory, process.cwd(), with targetDir will be the full path to where our static files are. Node.js needs a little extra code to serve our static files, and we want to make sure they’re there before we start the server1.

Serving static files from Node.js

The GWT compiler produces JavaScript embedded in some very long HTML files. From a server-side point of view these files are completely static. All the server needs to do is serve them. There are Node frameworks, such as Express, that can handle this for you, but we don’t need them for this simple example.

HelloSpiffyAuthNode.js, starting at line 51

var server = http.createServer(function(request, response) {
    /*
     * Handle based on the URI
     */
     var uri = url.parse(request.url).pathname;

    /*
     * Try to find a static file that matches the
     * current working directory/target/www/file name
     *
     * (process.cwd() gets the current working directory)
     */
     var filename = path.join(process.cwd(), targetDir, uri);  2
     path.exists(filename, function(exists) {

         /*
         * Serve up the static file that is in /target/www
         */

         if (filename.match('/www/$') !== null) {
             // Then this is a request for the root and we'll return
             // the index.html file
             filename += 'index.html'; 4
         }

         fs.readFile(filename, 'binary', function(err, file) {
            if (err) {
                response.writeHeader(500, {'Content-Type': 'text/plain'});
                response.end(err + '\n');
                return;
            }

            response.writeHeader(200);
            response.write(file, 'binary');  3
            response.end();
        }); //end fs.readFile callback

Joining process.cwd(), targetDir, and the uri, creates the full path and name of the static file to serve.2 We then read the file and write it to the response.3

We also handle the URI at the root and return the default home page index.html4. This simple function returns any of the static files, including those created by GWT. Now we can implement our REST endpoints.

A REST server in Node.js

Our project implements one REST call at /simple/<name entered by user>. Normally we would implement this as a separate function, but our example is so simple that we just put it inline.

HelloSpiffyAuthNode.js, starting at line 71

if (!exists) {
    if (uri.indexOf('/simple/') === 0) { 5
        /*
         * This is the REST call!
         */
        var user = uri.substring(8);
        var userAgent = request.headers['user-agent'];
        var payload = {user: user,
                       userAgent: userAgent,
                       serverInfo: 'Node.js'}; 6
        response.writeHeader(200, {'Content-Type': 'application/json'});
        response.end(JSON.stringify(payload));
        return;
    }

If the static file was not found, we test whether the uri is a REST request5. If it is, we return our simple JSON response. 6

Handling localization

Spiffy UI provides a couple of servlets for handling localization. We took the same basic algorithm for matching the list of the browser’s preferred locales with the list of supported locales for our application and put it into HelloSpiffyAuthNode.js.

This may sound complex, but the idea is fundamentally simple. The browser specifies a list of preferred locales with every request to the server. For example, the browser might say, I prefer French, then I’ll take German, and if you don’t have either of those give me English. This makes it possible for websites to do their best to provide the correct content.

Before we can even respond to the browser we have to figure out what languages we support. We do this by reading all the internationalized libraries included by Spiffy UI to localize dates and times.

HelloSpiffyAuthNode.js, starting at line 167

/*
 * Load all the internationalized resources by reading the i18n directory
 */
fs.readdir(path.join(process.cwd(), targetDir,  i18nDir), 
           function(err, files) {
    HelloSpiffyAuthNode.resources = files;7

    /*
     * After resources are retrieved then the server can listen
     */
    server.listen(port);

    sys.puts('=========================================================\n');
    sys.puts('Access Hello Spiffy Node at http://localhost:' + port + '\n');
    sys.puts('=========================================================\n');

});

All the files found in the i18nDir are stored in the HelloSpiffyAuthNode.resources variable.7 Then we add another simple condition to handle a localized date request.

HelloSpiffyAuthNode.js, starting at line 121

else if (uri === i18nDir + 'date' ||
         uri === i18nDir + 'jquery.ui.datepicker.js') {
    /*
     * This is an internationalized file request!
     */
    var resource  = HelloSpiffyAuthNode.getI18nDateResource(request, uri);
    filename = path.join(process.cwd(), targetDir, i18nDir, resource); 8
    /*
     * Do not return -- let it get the correct i18n date file below
     */
}

The getI18nDateResource function returns the best fit for the given list of preferred locales in the request’s accept-language header as compared with the locales embedded in the file names stored in the HelloSpiffyAuthNode.resources variable. The full path to the appropriate localized static date JavaScript file is produced at the end of this condition block8.

This simple example does not use any Messages class. If you want to add one so you can localize strings, you just need to add additional logic in your Node script to load the localized properties files then implement script to mimic the behavior of Spiffy UI’s locale filter. Hello Spiffy Localization is a good example of localizing a Spiffy UI project.

That’s everything we need to serve our Hello Spiffy Auth Node project. We haven’t changed our client and our whole server is about 150 lines of code. We can run the project now and serve anonymous requests. However, we’d also like to secure the REST endpoint.

Adding security

We want to add an authentication layer to secure our data. Spiffy UI provides a security scheme for REST endpoints. We’ll implement that scheme on our server and our client will work with just a few minor tweaks.

Adding security to our client

Spiffy UI handles client-side security as part of the REST framework. We add a little to our UI to show the user when they’re logged in and support log out.

We add the welcome banner and logout link to our MainHeader.

Index.java, starting at line 76

Anchor logout = new Anchor("Logout", "#");
logout.getElement().setId("header_logout");
header.setLogout(logout);
if (!Index.userLoggedIn()) {
    logout.setVisible(false);
    header.setWelcomeString("");
} else {
    header.setWelcomeString("You are logged in!");
}
logout.addClickHandler(new ClickHandler() {
        public void onClick(ClickEvent event)
        {
            event.preventDefault();
            doLogout();
        }
    });

Also, a login listener is added so the welcome banner can show the user name when the user logs in.

Index.java, starting at line 129

RESTility.addLoginListener(new RESTLoginCallBack() {

    @Override
    public void onLoginSuccess()
    {
        if (RESTility.getUserToken() == null) {
            return;
        }
        header.setWelcomeString("Welcome " + RESTility.getUsername());
        JSUtil.bounce("#" + MainHeader.HEADER_ACTIONS_BLOCK, 5, 500, 30);
        JSUtil.show("#header_logout", "fast");
    }

    @Override
    public void loginPrompt()
    {
        //do nothing
    }
});

The supporting userLoggedIn and doLogout methods simply call methods provided by Spiffy UI’s RESTility.

Index.java, starting at line 220

/**
 * returns whether the  user is logged in or not
 * @return true if the user is logged in (browser cookie is there)
 */
public static boolean userLoggedIn()
{
    String userToken = RESTility.getUserToken();
    if ((userToken == null) || (userToken.length() <= 0)) {
        return false;
    }
    return true;
}

/**
 * Logout of the application
 */
public static void doLogout()
{
    RESTility.getAuthProvider().logout(new RESTObjectCallBack() {
        public void success(String message)
        {
            Window.Location.reload();
        }

        public void error(String message)
        {
            Window.Location.reload();
        }

        public void error(RESTException e)
        {
            MessageUtil.showFatalError(e.getReason());
        }
    });
}

That’s all we need to make our client aware of authentication. The next step is to add security to our server.

Adding security to our server

When the REST request is made, we must first check that the request is authorized by looking at the request’s authorization header.

HelloSpiffyAuthNode.js, starting at line 72

if (uri.indexOf('/simple/') === 0) {
    if (isAuth(request.headers['authorization'])) {11
        /*
         * This is the REST call!
         */
        var user = uri.substring(8);
        var userAgent = request.headers['user-agent'];
        var payload = {user: user, 
                       userAgent: userAgent, 
                       serverInfo: 'Node.js'};
        response.writeHeader(200, {'Content-Type': 'application/json'});
        response.end(JSON.stringify(payload));
        return;
    } else {
        /*
         * Return the unauthenticated fault and include the header 
         * for authentication
         */
        response.writeHeader(401, {'Content-Type': 'application/json',
            'WWW-Authenticate': 'X-OPAQUE uri="http://localhost:' + port + 
            authUri + '", signOffUri=""'}); 9
        var fault = {'Fault': {'Code': {'Value': 'Sender', 'Subcode': 
            {'Value': 'NoAuthHeader'}}, 'Reason': {'Text':''}}};
        response.end(JSON.stringify(fault));
        return;
    }
}

If authenticated, the regular payload is returned, otherwise the fault payload is returned with the WWW-Authenticate header for the authentication server.9 In this example the authentication server is the same Node server at authUri.

In the UI, you’ll see this in action when you click the “Submit” button. If you are not authenticated, you will be shown the login screen. When you click “Login,” the Spiffy UI framework will automatically POST a request to the URL /auth. It is up to the handler of /auth to continue to delegate authentication responsibilities to the URL specified by the WWW-Authenticate header. In our example, we know that /auth is the same as the authUri, so we by-pass the delegation and just handle it directly.

Now we add code to our Node server to handle the posted authentication request.

HelloSpiffyAuthNode.js, starting at line 99

else if (uri === authUri) {
    if (request.method === 'POST') {
        /*
         * Any username and password combination is fine for this
         * sample, return the token, which is just the timestamp.
         */
        var token = '' + new Date().getTime()
        var json = {'Token': token};
        tokens.push(token);
        response.writeHeader(200, {'Content-Type': 'application/json'});
        response.end(JSON.stringify(json)); 10
        return;
    } else if (request.method === 'DELETE') {
        var authHeader = request.headers['authorization'];
        removeToken(authHeader);
        response.writeHeader(200, {'Content-Type': 'application/json'});
        
        var json = {'Status': 'OK'};
        response.end(JSON.stringify(json)); 12
        return;
    }
}

If the request’s method is POST, we know that it is a request to authenticate. In this example, we just accept any username and password combination, keep track of the new token, and return the token back to the browser.10 Spiffy UI will then automatically re-issue the original REST request with the authentication token in its authorization header, so our Node server will validate it11 and return the expected /simple payload. In the browser you will then see the username you entered in the welcome banner and the results of the /simple REST request will be shown.

Lastly, we need to handle when you click “Logout.” Spiffy UI will send a DELETE request with the token in its authorization header, so our Node server removes the token from its list of authorized tokens and sends back an “OK” response12.

Next steps – adding real security

This example accepts any username and password as valid. It works well for the example, but it isn’t very secure. The next step is adding a real token and connecting with a real security provider. This security scheme is an excellent fit for SAML tokens.

The stateless nature of the SAML security scheme scales well and works very well with the REST architecture of Spiffy UI’s tokenized identity. SAML is well supported by many security providers, but it’s not the only option. Spiffy UI supports any token type you want to use.

The client for every server

GWT is a great technology for rich web applications, but Java isn’t the right language for every server. When you put Spiffy UI in the front and REST in the middle the backend is any server that works for you. We’ve created Spiffy UI applications working against PHP, Erlang, and now Node.js. Spiffy UI will work with .NET, Python, or any application server you like.

Bess Siegal is a software engineer at NetIQ and a founding contributor to Spiffy UI. She enjoys her one-minute commute so she can spend more time with her husband and 3 daughters.

{ 1 comment }