Phone number not returned in user profile from Google OAuth

Hello, I’m trying to set up a very basic signup/login flow via Google OAuth. We have a separate flow for email/password signup we handle outside of Auth0; we only want to perform Google OAuth through Auth0 for now.

We use auth0.js on the client application to (1) authorize the user to retrieve an access token and (2) make a GET /userinfo request immediately after receiving the token from the hash parameters upon redirect. Once we retrieve the user information, we create an account on our backend outside of Auth0.

The user is able to enter the flow and the token is returned correctly, though the user profile returned from the GET /userinfo endpoint does not include a user’s phone number even though it is specified in the scope parameter.

Things I’ve tried:

  1. Followed all steps in https://auth0.com/docs/connections/social/google
  2. Verified our application through Google’s OAuth verification process to accept the https://www.googleapis.com/auth/user.phonenumbers.read scope
  3. Set the Client ID and Client Secret from our Google OAuth credentials in the Auth0 social connection config
  4. Passed the scope parameter in both the auth0.WebAuth({ ... }) constructor and the webAuth.authorize({ ... }) call.
  5. Used the following phone-related scopes: phone, user.phonenumbers.read, and https://www.googleapis.com/auth/user.phonenumbers.read
  6. Verified that the accounts I’m using to test have a verified phone number on their Google account.

Below is the authorization screen that appears on the first signup with a user - note that it informs the user that it will be retrieving their phone number.

Below is the response from GET /userinfo:

The Auth0 social connection configuration only requests the Basic Profile and Extended Profile attributes, with no permissions. Below is the response from testing the connection through the social connection’s config page - note that it contains neither the email nor the phone number (although I’m not sure if that’s expected or not).

Below is the code for both parts:

import auth0 from 'auth0-js';
import config from '@/config';

const webAuth = new auth0.WebAuth({
  domain: config.services.auth0.domain,
  clientID: config.services.auth0.clientId,
  // scope: 'openid profile email phone',
});

// Redirects the user to signup via Google OAuth, then redirects to /login
async function googleOAuthSignup() {
  webAuth.authorize({
    redirectUri: config.services.auth0.signupRedirectUri,
    connection: 'google-oauth2',
    responseType: 'token',
    scope: 'openid profile email phone',
  }, (err) => {
    console.error(err);
    return false;
  });
}

// Retrieves a user's Google profile info using their access token
async function getOAuthUserInfo(accessToken) {
  console.log(accessToken);
  return new Promise((resolve, reject) => {
    webAuth.client.userInfo(accessToken, (err, user) => {
      if (err) reject(user);
      else resolve(user);
    });
  });
}

export { googleOAuthLogin, googleOAuthSignup, getOAuthUserInfo };

I’m not sure what else to try; am I missing something fundamental?

Hey @mattrasto, Welcome to the Auth0 Community.

In our Basic and Extended profiles we do not provide phone number, if you hover over the
Basic and Extended profiles it mentions the fields listed and does not include phoneNumber.

But I have a workaround for you which worked for me when i tested on my end
Its not straightforward and required rules to be implemented to get the phoneNumber from the
google People API for the current user.

Also there is a prerequisite to get the phone number from google(for any App), the google user must have allowed the phone number to be seen for that user. Google account → Personal Info → About me → Contact Info(Phone number should be allowed to be seen)

Once you have that in place, first you need to add the permission(scope) in the /authorize request of the App. Which scope will depend on what you want(for phone number you need : “https://www.googleapis.com/auth/user.phonenumbers.read”)

In your app you need to add the following parameter in the /authorize request:

connection_scope = “https://www.googleapis.com/auth/user.phonenumbers.read

Check:Identity Provider Access Tokens
Google requires: “connection_scope”

It should be passed to google as the required scope, this will make sure google returns you the
access token(IDP access token) with the correct permission.

You can in fact check the access token returned to you by the IDP by the management API :
Its present in the User profile, above link(Identity Provider Access Tokens)
Mentions about that too.

If you are curious , There is a google api also which you can use to decode the access token and see if google has returned you the Correct permissions :

More information on that here : web services - How can I verify a Google authentication API access token? - Stack Overflow
Thats the API : https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=accessToken

Coming Back to the original topic, once you have added the connection_scope in the authorize request and you get back the Google access token, you can use this access token in the Rules to call the Google People API and get the phoneNumber!
I have provided you the Sample rule also you can refer, it worked for me well:

function (user, context, callback) {
  
  // only do this for users from Google connection
  // or maybe for a specific connection
  if (user.identities[0].provider !== 'google-oauth2')
    return callback(null, user, context);

  // To call Google Public API we need an access token
  //  - The access token given to the logged in user by the IdP (provided that it has
  //    the necessary permissions)

  // for simplicity, we will use the user's IdP access token
  var google_access_token = user.identities[0].access_token;

  console.log(google_access_token);
  // call Googles  public api to get information about the user
  var baseUrl = 'https://people.googleapis.com/v1/people/me?personFields=phoneNumbers';
  
  console.log('baseUrl:' + baseUrl);
  var getAADProfile = (callback) => {
    var options = {
      url: baseUrl,
      headers: {
        'Authorization': 'Bearer ' + google_access_token
      }
    };
    console.log('Requesting to '+ options.url);
    request(options, (err, response, body) => {
      if(err) {
        console.log("Error when calling "+ options.url);
        console.log(err);
            callback(err, null);
      } else {
            const profile = JSON.parse(body);
        	   console.log(profile);
            callback(null, profile);
      }
    });
  };

  getAADProfile((err, profile) => {
    if(err) {
      callback(err);
    } else {
      const namespace = 'https://sidc.example.com/';
      user.user_metadata = user.user_metadata || {};
      user.user_metadata.phoneNumber = profile.phoneNumbers[0].value;
      context.idToken[namespace + 'Phone_Number'] = user.user_metadata.phoneNumber;
       //You can fill other values also
      auth0.users.updateUserMetadata(user.user_id, user.user_metadata).then(() => {
        callback(null, user, context);
      }).catch((err) => {
        callback(err);
      });
    }
  });
} 

Once you have Permissions in place in the Google Access token and this rule , it should ideally populate the id token with the phone number.

Do try it and let me know how you go!!

Regards,
Sidharth Chaudhary

4 Likes

Cheers for the detailed response! I’ll try this tomorrow and update on results.

1 Like

Sure @mattrasto, Looking forward to it!

1 Like

Still running into a bit of trouble - the high level makes sense, and adding the connection_scope parameter properly informs the user that we’re requesting phone numbers in the consent screen. However, the rule seems to error out with:

{
  error: "access_denied",
  error_description: "Cannot%20read%20property%20'0'%20of%20undefined",
  state: "S5O...4r1"
}

Looks like user.identities in the rule is undefined for some reason. Not sure what user.identities is supposed to be (looks like there’s a markdown error on User Object Properties in Rules)

I also wasn’t able to use the Google API call to decode the access token - according the SO answer, it looks like they use a new endpoint (https://oauth2.googleapis.com/tokeninfo?id_token={token}), but neither one works - not a big deal, but makes it kind of hard to debug. I tried pasting the returned access token into both, and it returned:

{
  "error": "invalid_token",
  "error_description": "Invalid Value"
}

Main question at this point: what is user.identities and why might it be undefined? Is it possible the access token is somehow invalid, as indicated by the Google API? We can still retrieve GET /userinfo properly if I disable the rule, so I’m not too confident on that idea.

Hey @mattrasto, I am suspecting this error is returned from this part of the rules:
ser.user_metadata.phoneNumber = profile.phoneNumbers[0].value;

Because you are not getting the phone number in return for that google people API call.

There can be multiple reasons for this:
You google user has not exposed the phone number in its user profile(There is a way to check that I’ll elaborate it)
or
Your google access token does not contain the required permissions(How to get the google access token is different from Auth0 access token, its stored in User object of Auth0, you will need to access it and check the google access token)

1 Like

@mattrasto, continuing possible error reasons(Will break it down to multiple posts for better reading)

Your google user has not exposed the phone number in its user profile(There is a way to check that I’ll elaborate it)
You can check this by directly testing on the Google API with your google user profile here:

In the above link you have to go to Try This API : Fill resource name as : people/me and personFields as phoneNumber . Once you click execute it will authenticate and you and provide the response. it should be something like this:

 {
      "resourceName": "people/<sample id>",
      "etag": "%<sample string>",
      "phoneNumbers": [
        {
          "metadata": {
            "primary": true,
            "source": {
              "type": "PROFILE",
              "id": "<sample id> "
            }
          },
          "value": "actual phone number here",
          "type": "home",
          "formattedType": "Home"
        }
      ]
    } 

If it does not provide this phoneNumbers array in the response means your google user needs to expose the phoneNumber in the user profile. Then that is the possible issue for the rules failing. In the rules rather than directly accessing the user.user_metadata.phoneNumber = profile.phoneNumbers[0].value;
You can comment this line out and directly first print the access token(user.identities[0].access_token) and print the profile to see what is returned by the people API first.

1 Like

2nd possible reason for the error: Your google access token does not contain the required permissions(How to get the google access token is different from Auth0 access token, its stored in User object of Auth0, you will need to access it and check the google access token)

If the google IDP access token does not have the required permissions, people API will not return the phoneNumber also. Remember returned access token by app in auth0 is not the Google IDP access token its a Auth0 token You can either print the Google IDP access token in the rule to see the value (example above :console.log(google_access_token); ) or you can access it using the management API endpoint /api/v2/users/{user-id}, will be present in identities array, once you get it, you can verify the permissions using,(https://oauth2.googleapis.com/tokeninfo?id_token={token}).

Do debug these possible two reasons and let me know how you go!

1 Like

It works! :slight_smile:

What I did based on Sidharth’s advice, for others’ reference:

  1. I made sure to set the phone number public on one of my accounts and it was correctly returned by the Google People API correctly on their Try This API sidebar.
  2. I added a check to the rule to avoid erroring on missing phone number
  3. It was still failing so I installed the Auth0 Real-time Webtask Logs extension and noticed I was getting this error: People API has not been used in project <some project id> before or it is disabled...
  4. I enabled the People API in the GCP project we use for OAuth, and then tried again after a few minutes

Thanks a ton for all your help Sidharth! Also learned quite a bit about OAuth and Auth0 along the way thanks to the detailed responses.

1 Like

Great :+1: I understand its a lot of moving parts, but its rewarding at the end!

One last question:

Is there no way to retrieve the user’s phone number without using the People API? If the user has consented to sharing their phone number, I’m confused on why Google forces us to query the People API for it and prevents us from retrieving it if it’s not public. If this is explained by Google’s stringent data protection practices, that’s fine, but just want to make sure there aren’t any other solutions that could provide better coverage (as I’d imagine the number of users that purposely set their phone number visibility to public is fairly low)

2 Likes

@mattrasto, I also checked for other possible solutions for this, my opinion also based on the same logic as yours but for the moment I don’t see any scope returning this information or any other API without the explicit user setting the info to be shared.

Gotcha, no worries - we can work with this. Appreciate the help :slight_smile:

1 Like

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.