node.jsgoogle-cloud-platformgoogle-oauthgoogle-my-business-apigoogle-my-business

Unauthorized Error when Fetching Google My Business accounts under an Authenticated Google Account


I am trying to list the businesses under an authenticated Google Account. I have done the Oauth2 flow and have gotten the object containing the access_token, id_token,refresh_token and expiry_date

Now when I tried listing the businesses under that account I keep getting Unauthorized Error.

Below is my flow:

initOAuth2Client() {
    return new google.auth.OAuth2(
        process.env.GOOGLE_CLIENT_ID,
        process.env.GOOGLE_CLIENT_SECRET,
        process.env.GOOGLE_REDIRECT_URL
    );
}

//Authorization Code
async authenticateAgainstGoogleMyBusiness() {
    let oauth2Client = module.exports.initOAuth2Client();
    const scopes = [
        'https://www.googleapis.com/auth/business.manage',
        'https://www.googleapis.com/auth/userinfo.profile'
    ];
    const state = ....
    const url = oauth2Client.generateAuthUrl({
        access_type: 'offline',
        scope: scopes,
        state: state,
        prompt: 'consent'
    });
}

and then for my Oauth Callback Handler I have this

async googleOAuthCallbackHandler(req, res) {
    let oauth2Client = module.exports.initOAuth2Client();
    let { code, state } = req.query;
    const oauth2Client = module.exports.initOAuth2Client();
    const { tokens } = await oauth2Client.getToken(code);
    //this is where I saved the tokens exactly as received.
}

  async fetchGoogleMyBusinessAccounts() { 
   let {access_token} = fetchTokenFromDatabase(); //This is how I retrieved the access tokens saved by the previous function.

    console.log(`Fetching GMB Accounts`);
    let accountsUrls = `https://mybusinessaccountmanagement.googleapis.com/v1/accounts`;
    try {
        let headers = {
            'Authorization': `Bearer ${access_token}`
        }
        let response = axios.get(accountsUrls, {
            headers: headers
        });
        let data = response.data;
        console.log(`GMB Accounts response = ${JSON.stringify(data, null, 2)}`);
    } catch (e) {
        console.log('Error retrieving GMB Accounts:Full error below:');
        console.log(e); //I keep getting Unauthorized even though I authorized the process on the oauth consent screen
    }
}

How can I fix this?


Solution

  • I think you should have a look at the sample for Google drive I dont know why Google doesnt release samples for all the APIs but they dont. I put this together by altering the sample for google drive a bit. I dont have node installed right now but this should be close

    Let me know if you have any issues and i can try and help you debug them.

    const fs = require('fs');
    const readline = require('readline');
    const {google} = require('googleapis');
    
    // If modifying these scopes, delete token.json.
    const SCOPES = ['https://www.googleapis.com/auth/business.manage'];
    // The file token.json stores the user's access and refresh tokens, and is
    // created automatically when the authorization flow completes for the first
    // time.
    const TOKEN_PATH = 'token.json';
    
    // Load client secrets from a local file.
    fs.readFile('credentials.json', (err, content) => {
      if (err) return console.log('Error loading client secret file:', err);
      // Authorize a client with credentials, then call the Google Drive API.
      authorize(JSON.parse(content), listFiles);
    });
    
    /**
     * Create an OAuth2 client with the given credentials, and then execute the
     * given callback function.
     * @param {Object} credentials The authorization client credentials.
     * @param {function} callback The callback to call with the authorized client.
     */
    function authorize(credentials, callback) {
      const {client_secret, client_id, redirect_uris} = credentials.installed;
      const oAuth2Client = new google.auth.OAuth2(
          client_id, client_secret, redirect_uris[0]);
    
      // Check if we have previously stored a token.
      fs.readFile(TOKEN_PATH, (err, token) => {
        if (err) return getAccessToken(oAuth2Client, callback);
        oAuth2Client.setCredentials(JSON.parse(token));
        callback(oAuth2Client);
      });
    }
    
    /**
     * Get and store new token after prompting for user authorization, and then
     * execute the given callback with the authorized OAuth2 client.
     * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
     * @param {getEventsCallback} callback The callback for the authorized client.
     */
    function getAccessToken(oAuth2Client, callback) {
      const authUrl = oAuth2Client.generateAuthUrl({
        access_type: 'offline',
        scope: SCOPES,
      });
      console.log('Authorize this app by visiting this url:', authUrl);
      const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
      });
      rl.question('Enter the code from that page here: ', (code) => {
        rl.close();
        oAuth2Client.getToken(code, (err, token) => {
          if (err) return console.error('Error retrieving access token', err);
          oAuth2Client.setCredentials(token);
          // Store the token to disk for later program executions
          fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
            if (err) return console.error(err);
            console.log('Token stored to', TOKEN_PATH);
          });
          callback(oAuth2Client);
        });
      });
    }
    
    /**
     * Lists the names and IDs of up to 10 files.
     * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
     */
    function listFiles(auth) {
      const service = google.mybusinessaccountmanagement({version: 'v1', auth});
      service.accounts.get({
        pageSize: 10,
        fields: '*',
      }, (err, res) => {
        if (err) return console.log('The API returned an error: ' + err);
        const accounts= res.accounts;
        if (accounts.length) {
          console.log('accounts:');
          accounts.map((account) => {
            console.log(`${account.name} (${account.id})`);
          });
        } else {
          console.log('No files found.');
        }
      });
    }
    

    from comments

    Lets have a look at this error message from the comments.

    code: 429, errors: [ { message: "Quota exceeded for quota metric 'Requests' and limit 'Requests per minute' of service 'mybusinessaccountmanagement.googleapis.com' for consumer 'project_number:xxxxx'.", domain: 'global', reason: 'rateLimitExceeded' } ] }

    When you created your project on google cloud platform you had to enable the api you were going to use.

    enter image description here

    If you go back and check the it by (tip manage button) on the left there is a note about Quota

    enter image description here

    Each api has a default quota for when you enable it. The default quota for this api is

    enter image description here

    Quota is what controls how many requests you can make to the api. Google limits us because they want us all to be able to use the api. They dont want just one app flooding the system.

    As you can see the default quota for this api is 0 Which is why you are exceeding the limit already without making any requests.

    At the top of this page there is a note that says

    Request more quota limits or view quotas for your other services on the Quotas page, found in IAM & admin.

    After a lot of digging i managed to find a link to the form for requesting additional quota Usage limits at the very bottom of the page there is a line that says

    If you need higher limits, you can submit a Standard quota request.

    Submit the form. I have no idea how long its going to take you to get a quota extension.