How do I retrieve the granted scopes provided by Google

We are using the connection_scope parameter in our system to request the following scopes from google social connection (setting connection = "google-oauth2"):

https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/contacts.readonly

Is there a way to check if the user declines any of the scopes? Our app is a google calendar client and so we would like to be able to check right after login if the calendar scope was not granted since without it our app is useless.

According to google the there should be the list of granted scopes in the access token response. However, I can’t seem to find those anywhere in auth0, is there a way to retrieve those scopes? And ideally, propagate those to the auth0 access token response?

We may also not included the contacts scope for the first version of our app but instead request it in a future version requiring us to do incremental auth 1. So I have tried including the granted include_granted_scopes parameter but that didn’t seem to help.

I have pasted the rule I am using currently as a workaround onto the end of this post. This rule uses the https://oauth2.googleapis.com/tokeninfo endpoint to fetch the scopes after login to store the scopes into the metadata (we are also looking to providing multi-account support in the future by linking users which is we the scopes are stored as a dictionary with the key being the identity id)

Side Question: In a similar vein, there a way to make the refresh token provided by google persistent? Since google refresh, token id is only provided on the first authorization subsequent logins appear to erase the refresh token from the identities array in the user object. For this reason, the attached rule (along with fetching the scopes) also copies the refresh token from the identities array into the user_metadata in order to make it persistent. I know this is not recommended 2 (at least for app_metadata but I assume it is the same for user_metadata), but I can’t think of another way to easily make that refresh token persistently available.

Any help with either of these questions would be greatly appreciated :slight_smile: .

References (To get around the 3 link limit for new users :stuck_out_tongue: :wink: ):

  1. https://developers.google.com/identity/protocols/oauth2/web-server#incrementalAuth
  2. https://auth0.com/docs/best-practices/metadata-best-practices#app-metadata-restrictions

Persist Granted Scopes and Refresh Token Rule:

function (user, context, callback) {
  var getTokenInfoUrl = 'https://oauth2.googleapis.com/tokeninfo';
  var rp = require('request-promise');
  
  console.log("========== Persist Refresh Token and Scopes =========");

  user.user_metadata = user.user_metadata || {};
  user.user_metadata.refresh_tokens = user.user_metadata.refresh_tokens || {};
  user.user_metadata.scopes = user.user_metadata.scopes || {};
  
  var fetchScopesFromGoogle = function(accessToken) {
    return new Promise((resolve, reject) => {
      return rp({
        uri: getTokenInfoUrl,
        qs: {
          access_token: accessToken
        },
        json: true
      }).then(body => {
        resolve(body.scope.split(" "));
      }).catch(error => {
        console.error('error:', error);
        reject(error);
      });
    });
  };

  var persistMetaData = function() { 
    console.log(user);
    return auth0.users.updateUserMetadata(
      user.user_id,
      user.user_metadata
    ).then(function() {
      callback(null, user, context);
    }).catch(function(err) {
      callback(err);
    });
  };
    
  var promises = Promise.all(user.identities.map((identity, idx) => {
    if ('refresh_token' in identity) {
      var identity_id = identity.provider + '|' + identity.user_id;

      // Persist refresh token in the meta data since auth0 seems to drop the
      //  the refresh tokens from the identities array after subsequent logins,
      //  likely due to: 
      //    https://stackoverflow.com/questions/10827920/not-receiving-google-oauth-refresh-token
      user.user_metadata.refresh_tokens[identity_id] = identity.refresh_token;
      
      // Request the granted scopes for the token and add to metadata so we can
      //  have a record of the scopes that were actually granted for this refresh token
      return fetchScopesFromGoogle(identity.access_token).then(scopes => {
        user.user_metadata.scopes[identity_id] = scopes;
      });
    }
  })).then(persistMetaData, persistMetaData); // No finally in Node v8
}
1 Like

I am also wondering this – the /tokeninfo endpoint from google is not really documented as far as I can tell, and when it is mentioned (like here) it’s called out that it should be used for development and debugging.

It almost seems like the only “sanctioned” way to test scopes for any third-party connection is to try hitting an API and checking for failure, which I’d prefer not to do (and is not always trivial).

Has anyone come up with a better solution for this?

I hate Google’s APIs… there’s so much undocumented and very little P2P support for this. Like it took 9 months for some one to respond to the original question and now 6 months for an actual answer…

Anyways, I did a bit of research and they have this documentation page for using .tokeninfo().

// Before running the sample:
// - Enable the API at:
//   https://console.developers.google.com/apis/api/oauth2.googleapis.com
// - Login into gcloud by running:
//   `$ gcloud auth application-default login`
// - Install the npm module by running:
//   `$ npm install googleapis`

const {google} = require('googleapis');
const oauth2 = google.oauth2('v2');

async function main() {
  const auth = new google.auth.GoogleAuth({
    // Scopes can be specified either as an array or as a single, space-delimited string.
    scopes: [],
  });

  // Acquire an auth client, and bind it to all future calls
  const authClient = await auth.getClient();
  google.options({auth: authClient});

  // Do the magic
  const res = await oauth2.tokeninfo({
    access_token: 'placeholder-value',

    id_token: 'placeholder-value',
  });
  console.log(res.data);

  // Example response
  // {
  //   "audience": "my_audience",
  //   "email": "my_email",
  //   "expires_in": 0,
  //   "issued_to": "my_issued_to",
  //   "scope": "my_scope",
  //   "user_id": "my_user_id",
  //   "verified_email": false
  // }
}

main().catch(e => {
  console.error(e);
  throw e;
});
3 Likes

Thanks for sharing that with the rest of community!

Hey, here is another approach to @TigerYT solutions using the IDP access token in the user identities

import { google } from 'googleapis';

const auth = new google.auth.OAuth2(
// client id
  'XXXX-YYYY.apps.googleusercontent.com',
  CLIENT_SECRET,
);

const accessTokenInfo = await auth.getTokenInfo(
  // TODO: Make this into a proper helper
  // Note: Remember that you need to get this `user` from the Auth0 ManagementClient
   user.identities.find((idp) => idp.provider === 'google-oauth2' ),
);

console.log(accessTokenInfo)

----
{
  expiry_date: '...',
  scopes: [
    'https://www.googleapis.com/auth/drive.readonly',
    'https://www.googleapis.com/auth/userinfo.email',
    'https://www.googleapis.com/auth/userinfo.profile',
    'openid'
    // your others scopes, etc ...
  ],
  azp: '...',
  aud: '',
  sub: '...',
  exp: '...',
  email: '...',
  email_verified: 'true',
  access_type: 'online'
}
----

I was also wondering what it was the proper way to flag/check certain scope on our users so that we don’t have to call the google api each time we need to check this nor statically save it in our db (and deal with the complexity of updating this and so on)

Hope it helps