I've recently run into an issue authorising a new Google App Script project, specifically one using the Cloud SQL admin API.
The same code exists in previously authorised GAS projects and works fine, but if I take a copy of the GAS project and try to run a function for the first time I'm unable to complete the authorisation process. The screens I'm going through are listed below:
- Authorisation Required. - clicked "Review Permissions"
- Choose an account to authorise the Google project. - clicked my account
- This app isn't verified! - clicked "Go to project (unsafe)"
- Google project wants access to this list of scopes.- clicked "Allow"
- Authorisation is required to perform that action.
The warning screen (3) is a recent addition to the process. I don't rememeber encountering it when I've created and run new projects earlier this year. I'm wondering if Google has made any changes to their security implementation of OAuth2.0 recently.
Also, this issue only seems to affect REST calls to the Cloud SQL admin API. In the same project mentioned above I am able to run functions which write data to BigQuery tables in the same Google project which is also hosting the Cloud SQL instances. Clearly some scopes and code can be made to work.
The "https://www.googleapis.com/auth/sqlservice.admin" scope is included in the list of those I requested and approve. I even tried manually editing the URL to add more scopes being requested and it still doesn't get me passed the "Authorisation is required to perform that action" screen.
Does anyone have any idea's?
EDIT:
The code in question which is triggering the authentication.
// Function to get the ip address of a given CloudSQL instance
function _getInstanceIpAddress_(projectId, sqlInstance) {
var token = _getAuthenticationToken_();
// Create the header authorisation
var headers = {
"Authorization": "Bearer " + token
};
// Create the Cloud SQL instances get parameters
var parameters = {
"method": "get",
"headers": headers,
"instance": sqlInstance,
"project": projectId,
"muteHttpExceptions": true
};
// Create the url of the sql instances get API
var api = "https://www.googleapis.com/sql/v1beta4/projects/" + projectId + "/instances/" + sqlInstance + "?fields=ipAddresses";
try {
// Use the url fetch service to issue the https request and capture the response
var response = UrlFetchApp.fetch(api, parameters);
// Extract the ip address of the instance from the response
var content = JSON.parse(response.getContentText());
return content.ipAddresses[0].ipAddress;
} catch(err) {
_log_('ERROR', 'Getting ' + sqlInstance + ' instance ip address failed: ' + err);
return null;
}
}
function _getAuthenticationToken_() {
// Check we have access to the service
var service = getService();
if (!service.hasAccess()) {
var authorizationUrl = service.getAuthorizationUrl();
_log_('INFO', 'Open the following URL and re-run the script: ' + authorizationUrl);
return;
}
Logger.log('Passed Authentication');
//Get the Access Token
return service.getAccessToken();
function getService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService('companyName-dev-service')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Set the scopes to request (space-separated for Google services).
// this is admin access for the sqlservice and access to the cloud-platform:
.setScope(
'https://www.googleapis.com/auth/sqlservice.admin ' +
'https://www.googleapis.com/auth/cloud-platform')
//Removed because this Should be covered by cloud-platform
//'https://www.googleapis.com/auth/devstorage.read_write '
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
.setParam('login_hint', Session.getActiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
// Forces the approval prompt every time. This is useful for testing,
// but not desirable in a production application.
.setParam('approval_prompt', 'force');
}
function authCallback(request) {
var cloudSQLService = getService();
var isAuthorized = cloudSQLService.handleCallback(request);
if (isAuthorized) {
_log_('INFO', 'Access Approved');
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
_log_('INFO', 'Access Denied');
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
}
If you think back about a year ago you may remember the Massive Phishing Attack Targets Gmail Users What you are seeing is googles response to that.
Web credentials that use specific scopes require that Google approve them before anyone but the developer who created the credentials in question can use it. It normally takes about a week to get approved or so Google says.
You didnt seen it before because this only recently hit App script OAuth client verification
We had a similar issue with the Google Compute Engine API. Setting the scopes explicitly in the appsscript.json file as per this article solved it for us: