NodeJS / Express on Azure Web Sites with a Azure Blob Session Store

Getting NodeJS and Express running on Azure Web Sites is really easy.  The Node/Express/Azure community is fantastic and provides wonderful guidance.  However, I ran into a few challenges and I wanted to document them here just in case it helps anyone else.

Express Sessions

Out of the box Express gives you in memory sessions.  When you scale your sites in Azure (love that feature) in memory sessions don’t work so great anymore.  I needed a central place to store the sessions.  Azure Cache or Redis makes a fast and expensive session provider.  I really wanted something that would be very cheap.  So I choose Azure Blob Storage and needed to write a customer provider.

The connect session documentation was a huge help and I used TJ Holowaychuk’s implementation of a Redis session store as a guide.  To get started I created an object with three functions get, set, and destroy.  Each function implements a call to Azure Blob Storage for basic CRUD operations for the session object.


//Implemented from https://github.com/expressjs/session
//                 https://github.com/tj/connect-redis

var azure = require("azure-storage");

function azureSessionStoreFactory(session) {
    var Store = session.Store;
    function AzureSessionStore(options) {
        this.config = options;        
        this.blobService = azure.createBlobService(options.storageAccountKey);
        options = options || {};
        Store.call(this, options);
    }
    AzureSessionStore.prototype.__proto__ = Store.prototype
    
    var azureSessionPrototype = AzureSessionStore.prototype;
    azureSessionPrototype.get = function (sid, callback) {
        var self = this;
        self.blobService.getBlobToText('websessions', sid, function (err, result) {
            if (err) {
                callback(err, null);
            } 
            else {
                callback(null, JSON.parse(result));
            }
        });
    }
    
    azureSessionPrototype.set = function (sid, session, callback) {
        var self = this;
        self.blobService.createBlockBlobFromText('websessions', sid, JSON.stringify(session), function (err, results) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, session);
            }
        });
    }
    
    azureSessionPrototype.destroy = function (sid, callback) {
        var self = this;
        self.blobService.deleteBlobIfExists('websessions', sid , function (err) {
            if (err) {
                callbak(err, null);
            }
            else {
                callbak();
            }
        });
    }

    return AzureSessionStore;
}

module.exports = azureSessionStoreFactory;

To use our new Azure Blob session store we can plug it into Express with the following code.

</pre>
var app = express();
var sessionStore = new sessionStorage({ storageAccountKey: cloudStorage.primaryStorageAccount });

app.use(session({
store: sessionStore,
secret: '5up3r s3cret p@ssw0rd',
resave : true,
saveUninitialized : true
}));

While this is not the fastest session store.  It is a whole lot cheaper than Azure Cache or hosting Redis on a Virtual Machine.

Check out the code on GitHub https://github.com/rocketcoder/connect-session-azure-blob

Getting NodeJS/Express on Azure Web Sites to only serve https

Azure has done a fantastic job of making it easy to deploy a NodeJS application to Azure Web Sites.  However, one thing that took a bit of searching to find is how to force Azure Web Sites to only serve https requests.  Fortunately this is really easy.  I found the answer on Scott Hanselman’s blog but I am also posting it here.

Add the following to you Web.config.  (In Azure Web Sites nodejs is hosted behind IIS and the web.config controls how IIS behaves.)

    <rewrite>
      <rules>
        <clear />
        <rule name="Redirect to https">
          <match url="(.*)"/>
          <conditions>
            <add input="{HTTPS}" pattern="Off"/>
            <add input="{REQUEST_METHOD}" pattern="^get$|^head$|^post$" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}"/>
        </rule>
        <rule name="app" enabled="true" patternSyntax="ECMAScript" stopProcessing="true">
          <match url="iisnode.+" negate="true" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
          <action type="Rewrite" url="bin\www" />
        </rule>
      </rules>
    </rewrite>

NodeJS / Express / PassportJS building a local security strategy

Getting security setup in Express with PassportJS had a slight learning curve to it for me.  PassportJS has a variety of strategies that they use to implement security.  I needed a custom security provider based on Azure Table Storage.  Azure Table Storage is cheap and lightweight, so it was a great fit for a developer on a budget.  With Passport the key object to implement is the User object.  User implements a few key functions findOne, save, and isValidPassword.  I also used the bcrypt package to encrypt the passwords.  If you are looking for a more indepth tutorial on how to use PassportJS check out http://scotch.io/tutorials/javascript/easy-node-authentication-setup-and-local

var azure = require("azure-storage");
var q = require('q');
var config = require('../config.js');
var bCrypt = require('bcrypt-nodejs');

function User() { }
   
User.UserFactory = function() {
    var user = {
        userName : "",
        password : "",
        enabled : true,
        email : "",
        validated : false
    };
    user.isValidPassword = User.isValidPassword;
    return user;
}
   

User.findOne = function (userName) {
    var tableService = azure.createTableService(config.storageAccountKey);
    var deferred = q.defer();   
    tableService.retrieveEntity('CloudUser', userName, 'true', function (err, result) {
        if (err) {
            if (err.statusCode === 404)
                deferred.resolve(undefined);
            else
                deferred.reject(err);
        }
        else {
            deferred.resolve(User.toUser(result));               
        }
    });
    return deferred.promise;
}
 
User.toUser = function (userEntity) {
    var user = User.UserFactory();
    user.userName = userEntity.PartitionKey._;
    user.enabled = userEntity.RowKey._;
    user.password = userEntity.password._;
    user.email = userEntity.email._;
    user.validated = userEntity.validated._;
    return user;
};
   
User.toUserEntity = function (user) {
    return {
        PartitionKey: { '_': user.userName },
        RowKey: { '_': 'true' },
        password: { '_': user.password },
        email: { '_': user.email },
        validated: { '_': user.validated }
    };
};
   
User.createUser = function (user) {
    var deferred = q.defer();
    var tableService = azure.createTableService(config.storageAccountKey);
    function create(user) {                       
        tableService.insertEntity('CloudUser', User.toUserEntity(user), function (err, result) {
            if (err) {
                deferred.reject({ status: false, reason: &quot;error creating user&quot;, err: err });
            }
            else {
                deferred.resolve({ status: true, reason: &quot;&quot; });
            }
        });
    }

    User.findOne(user.userName).then(function (result) {
        if (result && result.length > 1) {
            deferred.resolve({ status: false, reason: &quot;username exists&quot; });
        }
        else {
            create(user);
        }
    }).
    fail(function (err) {
            deferred.reject({ status: false, reason: "error creating user";, err: err});
    });

    return deferred.promise;
}
   
User.save = function (user) {
    var tableService = azure.createTableService(config.storageAccountKey);
    tableService.updateEntity('CloudUser', user, function (err, result) {
        if (err) {
            deferred.reject({ status: false, reason: "error creating user" });
        }
        else {
            deferred.resolve({ status: true, reason: "" });
        }
    });
}
   
User.isValidPassword = function (user, password) {
    return bCrypt.compareSync(password, user.password);
}
   
// Generates hash using bCrypt
User.createHash = function (password) {
    return bCrypt.hashSync(password);
}

module.exports = User;

Now that we a User object that can find, save and validate passwords – we need plug it into a local strategy.

module.exports = function (passport) {

passport.use('login', new LocalStrategy({
passReqToCallback : true
},
function (req, username, password, done) {
// check if a user with username exists or not
User.findOne(username).then(function(user){
if (!user) {
console.log('User Not Found with username ' + username);
return done(null, false, req.flash('message', 'User Not found.'));
}
// User exists but wrong password, log the error
if (!User.isValidPassword(user, password)) {
console.log('Invalid Password');
return done(null, false, req.flash('message', 'Invalid Password')); // redirect back to login page
}
// User and password both match, return user from done method
// which will be treated like success
return done(null, user);
}).fail(function (err) {
// In case of any error, return using the done method
return done(err);
});

})
);
}

Of course we will also need a strategy to sign up new users.

&lt;/pre&gt;
module.exports = function (passport) {
passport.use('signup', new LocalStrategy({
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function (req, userName, password, done) {

findOrCreateUser = function () {
// find a user in Mongo with provided username
User.findOne(userName).then(function (user) {
// already exists
if (user) {
console.log('User already exists with username: ' + username);
return done(null, false, req.flash('message', 'User Already Exists'));
} else {
// if there is no user with that email
// create the user
var newUser = User.UserFactory();

// set the user's local credentials
newUser.userName = userName;
newUser.password = User.createHash(password);
newUser.email = req.param('email');
newUser.firstName = req.param('firstName');
newUser.lastName = req.param('lastName');
// save the user
User.createUser(newUser).then(function (err) {
console.log('User Registration succesful');
return done(null, newUser);
}).fail(function (err) {
if (err) {
console.log('Error in Saving user: ' + err);
throw err;
}
});
}
}).fail(function (err) {
// In case of any error, return using the done method
if (err) {
console.log('Error in SignUp: ' + err);
return done(err);
}
});
};
// Delay the execution of findOrCreateUser and execute the method
// in the next tick of the event loop
process.nextTick(findOrCreateUser);
})
);
}

And finally plug the strategies into Passport with the following code.


// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());

// Passport needs to be able to serialize and deserialize users to support persistent login sessions
passport.serializeUser(function (user, done) {
done(null, user.userName);
});

passport.deserializeUser(function (id, done) {
User.findOne(id).then(function (user) {
done(null, user);
}).fail(function (err) {
done(err);
});
});

// Setting up Passport Strategies for Login and SignUp/Registration
login(passport);
signup(passport);

You can checkout the code at https://github.com/rocketcoder/passportjs-AzureTableStorge

NodeJS – more than just a totally awesome web app platform.

So I needed to create a monitoring app that would hit a RESTful interface that required authentication. The only way to hit this RESTful service was to POST to a login page and then manage the authentication cookies for the remainder of the session. And I needed to get this done fast.

This is why I love NodeJS. NoneJS is more than a totally awesome web app platform. It makes for a great client application too.

NodeJS for the command line

The following is the basic code for kicking of an https request. The options argument contains the url, method, and any headers (including cookies). The data argument is for any data that needs to be POSTed with the request. (Make sure you set the appropriate headers for the data like content-type).


var cookie = "";
function processRequest(options, data){
  var req = https.request(options, function(res) {
    res.on('data', function(data) {
      cookie = processResponse(data);
    });
  });
  if(data){
    //POST data get written here.
    //Not included in the options like Jquery
    req.write(data);
  }
  req.end();
  req.on('error', function(e) {
    console.error(e);
  });
}

The magic is in the processResponse function. This is where we capture the cookie and store it for the next request.


function processResponse(){
  var cookies = null;
  if(res.headers && res.headers["set-cookie"]){
    cookies = res.headers["set-cookie"].join(";");
    cookies = cookies.replace(/path=\/; HttpOnly;/g, "");
    cookies = cookies.replace(/ path=\/; HttpOnly/g, "");
    cookies = cookies.trim();
    cookies = cookies + " otherCookie=oatmealraisen";'
    return cookies;
  }
}

Finally we can create the options objects and pass them to the processRequest function.


var loginData = "UserName=XXXXXXXXX&Password=XXXXXXXX&RememberMe=false";
function Login(cookies){
 var headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': loginData.length };
 var options = { hostname: 'ACME.COM', port: 443, path: '/LogOn', method: 'POST', headers : headers }; return options;
}
function dataSummary(cookies){
  var headers = { Cookie: cookies };
  var options = { hostname: 'ACME.com', port: 443, path: '/DataSummary', method: 'GET', headers : headers };
  return options;
}
processRequest(login());
processRequest(dataSummary(cookie));

NodeJS as a https client. Now the next step is to hook up a send email function. And that is for another blog.

I am using NodeJS for a lot more than creating web apps

I am a ASP.NET MVC developer. I love .NET and C# – great products. But some times you just need something light weight. I find myself using NodeJS to create those one off projects for diagnostics and automation.

My web apps tend to be very JSON heavy. In fact we produce tons of JSON every week! NodeJS makes for a great interactive JSON inspection utility.

var fs = require("fs");
var data = JSON.parse(fs.readFileSync("data.json", "UTF-8"));

Now you are free to investigate the data object. This is particularly helpful when you are using the REPL.

How to use nitrous.io to create a quick and easy web server in less than 5 minutes

Nitrous.io is a cloud service provider that gives you a linux vm running node.js with an I?DE and terminal –  all from your browser.   In less than 5 minutes you can have a NodeJS web server up and running in the cloud.  Here is how:

1.  Sign up for a Free Nitrous.io account.

2.  Create a new box.  Be sure to select the NodeJs stack.

3.  From the terminal type:

mkdir web

cd web

npm install -g express

express

npm install

4.  Now you can start uploading your static content files to the public folder

5.  Start the node server, make sure you are in the web folder

cd /web

node app.js

Now your web site is running.  On the main menu click Preview and then select port 3000.  Now you can browse to your web page.

nitriuos

Why?  Why not use a real IDE?

Nitrous.IO provides a full web app development environment from your browser.  Try running NodeJS on your IPad, Android or Win RT tablet – not an option.  This is powerful platform because we can be anywhere and within minutes can be hacking on a new idea from a mobile device.