10

I'm using this npm library - https://www.npmjs.com/package/googleapis and I'm using the following Express routes as /user/:

/* Redirect to the google login page */
  router.get('/login', function (req, res) {
    res.redirect(auth.generateUrl());
  });

  /* The callback from the google OAuth API call */
  router.get('/callback', function (req, res) {
    auth.authenticate(req.query.code);

    res.send();
  });

auth is this module:

var oAuth2 = require('googleapis').auth.OAuth2;

var oauth2Client = new oAuth2([CLIENT_ID], [CLIENT_SECRET], [DOMAIN] + '/user/callback');

module.exports = {
    /**
     * Generate a url to redirect to for authenticating via Google
     *
     * @return {String}
     */
    generateUrl: function () {
        return oauth2Client.generateAuthUrl({
            access_type: 'online', // 'online' (default) or 'offline' (gets refresh_token)
            scope: ['https://www.googleapis.com/auth/userinfo.email'] // If you only need one scope you can pass it as string
        });
    },
    authenticate: function (code) {
        oauth2Client.getToken(code, function (err, tokens) {
            console.log(err);

            // Now tokens contains an access_token and an optional refresh_token. Save them.
            if (!err) {
                console.log(tokens);

                oauth2Client.setCredentials(tokens);
            }
        });
    }
};

The authenticate function above is based on the example in https://www.npmjs.com/package/googleapis#retrieve-access-token.

Now if I go to /user/login I see the google login page, which then asks me for permission. I have used the email scope above but I do not see my email address in the tokens object that is returned. This is what I get:

{ access_token: '[72 length string]',
  token_type: 'Bearer',
  id_token: '[884 length string]',
  expiry_date: [integer timestamp] }

Is this not how to get the email address? The documentation is not very clear and neither are example tutorials I have found online, as they mainly deal with a specific service from google, like the calendars. I'm only interested in basic authentication. I can't find any other methods that might get scope information in the documentation.

A minor point as well, but do I have to call getToken() on every request when a user is logged in?

Edit:

After some digging around in the code for the library, I found this:

this.userinfo = {

    /**
     * oauth2.userinfo.get
     *
     * @desc Get user info
     *
     * @alias oauth2.userinfo.get
     * @memberOf! oauth2(v1)
     *
     * @param  {object=} params - Parameters for request
     * @param  {callback} callback - The callback that handles the response.
     * @return {object} Request object
     */
    get: function(params, callback) {
      var parameters = {
        options: {
          url: 'https://www.googleapis.com/oauth2/v1/userinfo',
          method: 'GET'
        },
        params: params,
        requiredParams: [],
        pathParams: [],
        context: self
      };

      return createAPIRequest(parameters, callback);
    }

This is in both node_modules/googleapis/apis/oauth2/v1.js and node_modules/googleapis/apis/oauth2/v1.js. However, this doesn't appear to be what require('googleapis').auth.OAuth2 uses, which is node_modules/google-auth-library/lib/auth/oauth2client.js. Is there a way of accessing userinfo.get?

Further Edit

I found this tutorial - https://www.theodo.fr/blog/2014/06/dont-bother-with-keys-open-your-door-with-google-api/, this section of which (near the bottom of the page) is exactly what I want to do:

googleapis.discover('oauth2', 'v1').execute(function(err, client) {
    if (!err) {
        client.oauth2.userinfo.get().withAuthClient(oauth2Client).execute(function(err, results) {
            var email = results.email;

            if ((email.indexOf('theodo.fr') + 'theodo.fr'.length) != email.length) {
                return res.send({
                    status: -1,
                    message: "Google Plus authentication failed (domain mismatch)"
                });
            }

            doorClient.open();

            res.send({
                status: 0,
                message: 'Door opened. Welcome !'
            });
        });
    }
});

Leaving aside the absolute ridiculous verbosity of Google's API, this code no longer works. discover is no longer a function so I've no idea how to access v1 or v2 which contain the userinfo.get function that I need.

Gnuffo1
  • 3,478
  • 11
  • 39
  • 53
  • If anyone else runs into this problem, I gave up and used passport with the google oauth strategy instead - https://www.npmjs.com/package/passport-google-oauth20 – Gnuffo1 Apr 21 '16 at 13:19

2 Answers2

11

With the version I have right not, which is 2.1.6, the way to make it work is:

googleapis.oauth2("v2").userinfo.v2.me.get({auth: oauth2Client}, (e, profile) => {
    ...
});

I have to looked into the source code to figure out how to do it and I am not 100% sure whether this is the best way because I have to mention "v2" twice. But it works for me.

Comtaler
  • 1,580
  • 1
  • 15
  • 26
  • you should pick whether to use `=>` or `function` for the callback, so it should look like `googleapis.oauth("v2").userinfo.v2.me.get({auth: oauth2Client}, (e, profile) => { ... });` – Andrew Stroup Jan 31 '17 at 03:46
  • @AndrewStroup It's a typo, I have corrected it. Thanks for pointing it out – Comtaler Jan 31 '17 at 18:25
4

My solution:

const google = require('googleapis');

const oauth2 = google.oauth2('v2');
const OAuth2 = google.auth.OAuth2;

exports.getUserInfo = (accessToken) => {
  // CLIENT_ID and CLIENT_SECRET are optional
  const authClient = new OAuth2(/* CLIENT_ID, CLIENT_SECRET */);

  authClient.setCredentials({
    access_token: accessToken,
  });

  return new Promise((resolve, reject) => {
    oauth2.userinfo.get({
      auth: authClient,
    }, (err, data) => (err ? reject(err) : resolve(data)));
  });
};
Moshe Simantov
  • 3,937
  • 2
  • 25
  • 35
  • 3
    after 3 years the docs of googleapis are still confusing. The difference between `oauth2 = google.oauth2('v2');` and `OAuth2 = google.auth.OAuth2;` is crucial. I was trying to use `OAuth2` instead of `oauth2` when trying to call `userinfo.get`. After hours of searching, this answer gave me relief. – Rezwan Azfar Haleem Aug 25 '20 at 09:34