Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

How do I use node-openid on Heroku with more than one dyno?

How do I use node-openid on Heroku with more than one dyno?

Problem

I'm trying to use node-openid (via passport-google) to authenticate my users with their Google credentials. It works fine on my development machine but when I deploy it to Heroku with 2 dynos, it works when one dyno handles the entire OpenID conversation and fails when the conversation is started on one dyno and completed on the second. In that case I get the following error:

2013-01-15T15:18:24+00:00 app[web.2]: Failed to verify assertion (message: Invalid association handle)
2013-01-15T15:18:24+00:00 app[web.2]:     at Strategy.authenticate.identifier (/app/node_modules/passport-google/node_modules/passport-openid/lib/passport-openid/strategy.js:143:36)
...

What's the right way to handle this? Should I be saving the conversation state in a database somehow so that both dynos have access to it?

Update:

Here's the code I used to solve the problem by storing the assocation in MongoDB.

var
  GoogleStrategy = require('passport-google').Strategy;

// We have to save the OpenID state in the database so it's available to both
// dynos.

db.collection('OpenID').ensureIndex({expires: 1}, {expireAfterSeconds: 0},
    function(err, result) {
        if (err) {
            throw new Error('Error setting TTL index on OpenID collection.');
        }
    });

// Use the GoogleStrategy within Passport.
//   Strategies in passport require a `validate` function, which accept
//   credentials (in this case, an OpenID identifier and profile), and invoke a
//   callback with a user object.
strategy = new GoogleStrategy({
    returnURL: 'http://localhost:3000/auth/google/return',
    realm: 'http://localhost:3000/'
  },
  function(identifier, profile, done) {
    // asynchronous verification, for effect...
    process.nextTick(function () {

      // To keep the example simple, the user's Google profile is returned to
      // represent the logged-in user.  In a typical application, you would want
      // to associate the Google account with a user record in your database,
      // and return that user instead.
      profile.identifier = identifier;
      return done(null, profile);
    });
  }
);

strategy.saveAssociation(function(handle, provider, algorithm, secret, expiresIn, done) {
    db.collection("OpenID").insert({
        handle: handle,
        provider: provider,
        algorithm: algorithm,
        secret: secret,
        expires: new Date(Date.now() + 1000 * expiresIn)
    }, done);
});

strategy.loadAssociation(function(handle, done) {
    db.collection("OpenID").findOne({handle: handle}, function (error, result) {
        if (error)
            return done(error);
        else
            return done(null, result.provider, result.algorithm, result.secret);
    });
});
Problem courtesy of: David Braun

Solution

You should probably check out the Storing association state section in the node-openid README. By default, session state is stored in-memory on the individual dynos and that's what's causing you problems.

Override the saveAssociation() and loadAssociation() mixins to use whatever backing store your application is currently using. There's more documentation in the passport-openid source code.

Solution courtesy of: friism

Discussion

View additional discussion.



This post first appeared on Node.js Recipes, please read the originial post: here

Share the post

How do I use node-openid on Heroku with more than one dyno?

×

Subscribe to Node.js Recipes

Get updates delivered right to your inbox!

Thank you for your subscription

×