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.
OAuth 2.0 Scopes:
(Admin SDK API is for another function which I will test next; it is not used currently.)
The above scopes are set as follows:
Script:
// Email address of the user to impersonate.
var USER_EMAIL = 'it@domain.org'; // 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";
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.