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