← home

Passport and Google's OpenID 2.0 to OAuth 2.0 migration

August 2nd 2014

Passport is a popular authentication middleware for Node.js. It has a plug-in system which supports more or less all popular authentication services. For Google it has passport-google and passport-google-oauth plug-ins. Passport-google is OpenID 2.0 based and passport-google-oauth is OAuth 2.0 based.

Google has announced that it terminates OpenID 2.0 support in 2015. If you are like me, you have for a long time offered your users a possibility to sign-up with their Google account using OpenID 2.0 behind the scenes. And are now forced to upgrade to OAuth 2.0.

There are two ways to handle the upgrade without losing track of user identities. The first, simple way is just to switch to OAuth 2.0 and ask user's primary email address from Google as part of the OAuth authentication transaction. You can then search the user from your database using the received email address. For me this doesn't work as my service allows users to change their email addresses. I'm not forcing that they must use their primary Google email addresses. The only thing I really know about them is their OpenID id, e.g.

https://www.google.com/accounts/o8/id?id=AItOawn7n9TjBjhPf5Eip-ld01Yep2NgGsNk32E

The second option is to switch to OAuth 2.0 and ask Google to return both user's OpenID and OAuth identifiers after a successful login. Then you can search the user from database using both of these ids. If OpenID id matches then save OAuth 2.0 id (called OpenID Connect identifier in Google's documentation) for the user and gradually every user will have OAuth id in the database. Google's migration guide is available but it doesn't provide step by step instructions for Passport. Here is how I did it.

First set OAuth 2.0 strategy like in the examples and test it works.

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

passport.use(new GoogleStrategy({
    clientID: GOOGLE_CLIENT_ID,
    clientSecret: GOOGLE_CLIENT_SECRET,
    callbackURL: "http://127.0.0.1:3000/auth/google/callback"
  },
  function(accessToken, refreshToken, profile, done) {
    User.findOrCreate({ googleId: profile.id }, function (err, user) {
      return done(err, user);
    });
  }
));

Then you need to add openid.realm parameter to the authentication request URI. This is step 1 in Google's migration guide. Passport-google-oauth doesn't support openid.realm option. I have opened a PR to fix it. In the meanwhile you need to monkey patch the authorizationParams method instead.

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

var google = new GoogleStrategy({
  clientID: GOOGLE_CLIENT_ID,
  clientSecret: GOOGLE_CLIENT_SECRET,
  callbackURL: "http://127.0.0.1:3000/auth/google/callback"
},
function(accessToken, refreshToken, profile, done) {
  User.findOrCreate({ googleId: profile.id }, function (err, user) {
    return done(err, user);
  });
});

// Monkey patch to support openid.real option
var originalAuthorizationParams = google.authorizationParams;

google.authorizationParams = function() {
  var val = originalAuthorizationParams.apply(this, arguments);
  val['openid.realm'] = REALM_YOU_USE_IN_OPENID_LOGINS
  return val;
};

passport.use(google);

With this change, Google returns both OpenID and OAuth ids. There is one final step needed to read the OpenID id. GoogleStrategy callback function

function(accessToken, refreshToken, profile, done) {
  User.findOrCreate({ googleId: profile.id }, function (err, user) {
    return done(err, user);
  });
}

has to be changed to

var jwt = require('jwt-simple'),

function(accessToken, refreshToken, params, profile, done) {
  var openIdId = jwt.decode(params.id_token, null, true).openid_id;
  var oAuthId = profile.id;

  // Search user using both IDs. If OpenID id matches then save OAuth id for that user.
}

This is the step 3 in Google's migration guide. Params parameter includes OAuth ID token. I am using jwt-simple node module to decode it.





comments powered by Disqus