I find heroku to be a great hosting platform for node.js applications - and one of the reasons being that node.js is natively supported by heroku’s stack, freeing me from most platform configuration worries. It is also worth mentioning how easy it is to scale applications by simply allocating more “dynos” (essentially, virtual machines) for your application. My framework of choice for node.js web applications is expressjs - I like its minimalism and flexibility.
Today, I want to talk about authentication in node.js apps hosted on heroku and demonstrate a way to maintain horizontal scalability with authentication implemented.
Most modern web applications require some sort of authentication, and, luckily, the Passport middleware for node.js does exactly that. Its flexibility not only allows us to implement our own authentication and serialization logic, it supports several authentication strategies out-of-the-box, including SSO with OAuth and OpenID.
More often than not, authentication requires sessions. When developing applications with expressjs as the platform, the default option of storing sessions in memory usually won’t cut it if we want to use more than a single application server - a particular session needs to be accessible from any application dyno. One way to solve this is by using a load balancer that provides “session stickiness” - a mechanism that ensures that the same backend server handles all requests for a particular session - however, this can cause all sorts of problems, and, besides, heroku’s load balancers do not provide such functionality yet. This nudges us to work in a different direction, architecting some sort of centralized storage for our sessions.
There are several ways to store sessions centrally, each with their own pros and cons. I personally prefer something lightweight, non-persistent - ideally, a key-value store with TTL (time-to-live), conveniently accessible from a node.js app. Something like Redis, which we will use as an example in this how-to.
First problem is setting up Redis on heroku. Luckily, redistogo.com offers Redis storage as a heroku add-on.
So, let’s summarize the plan to implement authentication in our app:
- Set up passport
- Set up Redis for session storage
- Profit
Let’s start with the first item on the list - setting up passport to handle our authentication needs.
$ npm install passport
Passport plugs into expressjs just as any other middleware, so invoking the actual authentication is quite simple:
app.post(‘/login’, passport.authenticate(‘local’), function(request, response) {
// authentication is successful - redirect somewhere else.
request.user contains the user information:
response.redirect(‘/some/other/route’);
});
In the example above, ‘local’ is our authentication strategy - we will talk about it shortly. Passport authentication middleware can be instructed to redirect in cases of success or failure, for example:
passport.authenticate(‘local’, {
successRedirect: ‘/‘,
failureRedirect: ‘/login'
})
There are even more examples of the passport middleware functionality in the documentation.
Now, our authentication strategy needs to be configured before it can be used, so lets take care of that:
passport.use(new AuthStrategy(function(username, password, done) {
// this is an example, finding user in the database
Users.findOne(username, password, function(error, user) {
// error finding the user - authentication failed
if(error) return done(error);
// user not found - authentication failed
if(!user) return done(null, false);
// additional checks for authentication are done here
// otherwise, authentication successful
return done(null, user);
});
});
We also need to provide user serialization/deserialization routines. In the simplest scenario, we let things handle themselves:
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(id, done) {
done(null, id);
});
In this example, the entire user object is serialized into the session store. Depending on the application logic, you may decide to only store a session id and look up the user by its id somewhere else. The two functions above will be the place to implement any (de)serialization logic we need.
Last thing - initialize passport and let it know about sessions (which, at this point, aren’t set up yet):
app.use(passport.initialize());
app.use(passport.session());
With our simple authentication logic all set up, there is only one missing piece in this puzzle - the actual session store. Moving on to part 2 of our plan - setting up Redis as our session store and using it in our app.
First, let’s add redistogo to our app:
$ heroku addons:add redistogo
That’s it. Your redis instance is ready. Installing the add-on automagically adds a REDISTOGO_URL environment variable to your app, and you will see it if you run:
$ heroku config
And you should see a line similar to this, among other configuration values:
REDISTOGO_URL:
redis://redistogo:7f87e90468f8b1d026ae5495b7735232@hoki.redistogo.com:9271/
Now, moving on to setting up the redis client in your code. I use the node_redis module for the Redis client, and the connect-redis module, which wraps the Redis client as a middleware usable with expressjs. Then, in the application code:
var redis = require('redis’);
var RedisStore = require('connect-redis')(express);
For the purpose of this tutorial and in sake of simplicity, let’s assume you have everything in meaningfully named constants. Let’s set up the session store - it will internally set up the Redis client.
// set the options here
var redisOptions = {
url: REDIS_URL,
ttl: SESSION_TTL
/* There are more options available here,
look ‘em up in node_redis documentation */
};
// create the Redis store, it will set up the Redis client internally
var redisStore = new RedisStore(redisOptions);
// and now, define express sessions with the store we just set up,
and some express options
session = express.session( {
store: redisStore,
secret: SESSION_SECRET,
unset: 'destroy',
proxy: true
});
// and use it in application
app.use(session);
The section above has to run before we initialize passport.
Don’t forget the last remaining pieces: handle logout, user profile, and anything else your app needs. The passport documentation is excellent: http://passportjs.org/guide/.
That’s it - your Redis-based session store is ready, we can now scale our dynos:
$ heroku ps:scale web=2
Now, with this solution, there is the Redis redundancy issue to keep in mind. While the free redistogo.com plan only offers a single database with no extra features, the paid plans do offer sharded setups with redundancy, backups and all the bells and whistles. Resolving the SPOF issue is is a matter of upgrading your plan.
As you see, all this can be done with minimal code. I hope this tutorial helps someone, and happy coding!