Home > OS >  How can I get past the "Insufficient authentication scopes" error in Google Apps Script?
How can I get past the "Insufficient authentication scopes" error in Google Apps Script?

Time:10-19

I am designing a Google Apps Script for domain-wide delegation, so that it can read details from Gmail in our Google Workspace domain's user accounts. The script fails even when it accesses my own Gmail.

"message": "Request had insufficient authentication scopes."
"errors ... ""message": "Insufficient Permission"
"reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT"

Being a super admin for our domain, I performed all the steps that follow.

  • My script is associated with a Google Cloud Platform (GCP) standard project.
  • I created a service account with access to the project. My roles: Project IAM Admin , Service account admin, Service account user, Service Account Token Creator.
  • The script includes the OAuth2 library.
  • I reset the authorization whenever I made a change, to force Google to ask again for authorization.

OAuth 2.0 Scopes:

  • "https://www.googleapis.com/auth/script.external_request"
  • "https://www.googleapis.com/auth/gmail.labels" (Gmail API)
  • "https://www.googleapis.com/auth/admin.directory.user.readonly" (Admin SDK API)

(Admin SDK API is for another function which I will test next; it is not used currently.)

The above scopes are set as follows:

  • Project manifest (appsscript.json)
  • OAuth consent screen for the project
  • Domain-wide delegation: Admin console > Security > API Controls > Domain-wide Delegation
  • Enable APIs for the standard Google Cloud project: Gmail, Admin SDK
  • The service account has "Trusted" access ( Admin console > Security > Access and data control > API controls > Manage Third-Party App Access > App Access Control ) to Gmail and external request, but not to Admin SDK (not currently used).
  • Set scopes in Code.gs: setScope refers to the first two scopes (Admin SDK is not currently used).

Script:

// Email address of the user to impersonate.
var USER_EMAIL = '[email protected]'; // use my address for testing the simplest case

function listLabels() {
  /**
   * References: 
   * https://developers.google.com/gmail/api/reference/rest/v1/users.labels/list
   * https://gmail.googleapis.com/$discovery/rest?version=v1
   * Similar script: https://developers.google.com/apps-script/advanced/gmail#list_label_information
   */
  try {
    const userID = '1x0gk371hhfknr'; // use my userID for now;
    var service = getService_('listLabels');    
    if (service.hasAccess()) {
      var url = 'https://gmail.googleapis.com/gmail/v1/users/'   userID   '/labels';
      Logger.log(url);
// Script fails here:
      var response = UrlFetchApp.fetch(
        url, {
          headers: { Authorization: 'Bearer '   service.getAccessToken() },
          muteHttpExceptions: true
        }
      );
      Logger.log('response: '  response);
      var result = JSON.parse(response.getContentText());
      Logger.log("result['labels']: "   JSON.stringify(result['labels'], null, 2)); 
      for (let i = 0; i < result['labels'].length; i  ) {
        const label = result['labels'][i];
        Logger.log(JSON.stringify(label));
      }
    } else {
      Logger.log( 'Service lacks access' );
    }
  } catch (err) {
    Logger.log('listLabels failed with: '   err);
  }
}

function getService_( serviceName ) {
/*
 * From https://github.com/googleworkspace/apps-script-oauth2/blob/main/samples/GoogleServiceAccount.gs
 * Implements domain-wide delegation 
 */

  return OAuth2.createService(serviceName)
    .setTokenUrl('https://oauth2.googleapis.com/token')
    .setPrivateKey(PRIVATE_KEY)
    .setIssuer(CLIENT_EMAIL)
    .setSubject(USER_EMAIL) // name of the user to impersonate

    // Set the property store where authorized tokens should be persisted.
    .setPropertyStore(PropertiesService.getScriptProperties())

    .setScope(
      "https://www.googleapis.com/auth/script.external_request",
      "https://www.googleapis.com/auth/gmail.labels"
    )
}

// Private key and client email of the service account. (I present fake values here.)
const PRIVATE_KEY =
    "-----BEGIN PRIVATE KEY-----\nMIIEv ... YNmQGU19FAcc=\n-----END PRIVATE KEY-----\n";
const CLIENT_EMAIL = "fifth-try@ ... .iam.gserviceaccount.com";

CodePudding user response:

Replace

.setScope(
      "https://www.googleapis.com/auth/script.external_request",
      "https://www.googleapis.com/auth/gmail.labels"
    )

by

.setScope(
      "https://www.googleapis.com/auth/script.external_request https://www.googleapis.com/auth/gmail.labels"
    )

Put the scopes in a single string, separated by spaces. The Readme.md from the GitHub repo of the Google Apps Script OAuth library states that.

  • Related