Coert Grobbelaar Coert Grobbelaar - 1 month ago 22
C# Question

Google apps script execution API service authorization fails once per hour

I'm executing a Google apps script with my C# app about once every 1.5 minutes. The apps script moves content between spreadsheets, and edits sheets. I also use the Drive API.

My script runs fine over long periods, except for the fact that I get an authorization errors for 5 minutes every hour.

Here is my code that handles authorization:

class Authentication
{

public static ScriptService ScriptsAuthenticateOauth(UserCredential credential)
{
try
{

ScriptService service = new ScriptService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MyApp",
});

return service;
}
catch (Exception ex)
{
Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": An authentication error occurred: " + ex.InnerException);
return null;
}

}


public static UserCredential getCredential(string clientId, string clientSecret, string userName)
{

string[] scopes = new string[] { DriveService.Scope.Drive, // view and manage your files and documents
DriveService.Scope.DriveAppdata, // view and manage its own configuration data
DriveService.Scope.DriveAppsReadonly, // view your drive apps
DriveService.Scope.DriveFile, // view and manage files created by this app
DriveService.Scope.DriveMetadataReadonly, // view metadata for files
DriveService.Scope.DriveReadonly, // view files and documents on your drive
DriveService.Scope.DriveScripts, // modify your app scripts
ScriptService.Scope.Drive,

"https://www.googleapis.com/auth/spreadsheets",
"https://spreadsheets.google.com/feeds",
"https://docs.google.com/feeds"};
return GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret }
, scopes
, userName
, CancellationToken.None
, new FileDataStore("Google.Sheet.Sync.Auth.Store")).Result;
}

public static DriveService DriveAuthenticateOauth(UserCredential credential)
{
try
{


DriveService service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MyApp",
});

// Console.WriteLine("Auth success");


return service;
}
catch (Exception ex)
{

// Console.WriteLine(ex.InnerException);
Console.WriteLine(DateTime.Now.ToString("HH:mm") + ": An authentication error occurred: " + ex.InnerException);
return null;

}

}
}


I get my services like this:

var credential = Authentication.getCredential(CLIENT_ID, CLIENT_SECRET, Environment.UserName);
DriveService driveService = Authentication.DriveAuthenticateOauth(credential);
ScriptService scriptService = Authentication.ScriptsAuthenticateOauth(credential);


But around the end of the hour, the apps script call throws the following error:

Script error message: Authorization is required to perform that action.


Just to be clear, it works at all other times, just not in those 5 minutes near the end of the hour. I did activate Google Drive API on both my developers console, and under Resources > Advanced Google services... in the apps script editor.

So what is going on? Can this be fixed?

Answer

Upon doing further research, I found that the script just needed to be run from the Script Editor to prevent this error. This is the info I found:

Authorization is required to perform that action.

This error indicates that the script is lacking the authorization needed to run. When a script is run in the Script Editor or from a custom menu item an authorization dialog is presented to the user. However, when a script is run as a service, embedded with a Google Sites page, or run from a trigger the dialog cannot be presented and this error is shown. To authorize the script, open the Script Editor and run any function. To avoid this error, remember to run the script once in the Script Editor after adding new services or capabilities to your script.

Source: https://developers.google.com/apps-script/troubleshooting#common_errors

Another related improvement I made to my code, was to fetch a fresh copy of the credentials object before creating each of the 2 services: In other words, I changed this:

var credential = Authentication.getCredential(CLIENT_ID, CLIENT_SECRET, Environment.UserName);
DriveService driveService = Authentication.DriveAuthenticateOauth(credential);
ScriptService scriptService = Authentication.ScriptsAuthenticateOauth(credential);

to this:

var credential = Authentication.getCredential(CLIENT_ID, CLIENT_SECRET, Environment.UserName);
DriveService driveService = Authentication.DriveAuthenticateOauth(credential);
credential = Authentication.getCredential(CLIENT_ID, CLIENT_SECRET, Environment.UserName);
ScriptService scriptService = Authentication.ScriptsAuthenticateOauth(credential);

This by itself did not fix my problem, but it helps with avoiding OAuth quirks, and can prevent unnecessary token refreshes.

Comments