Authentication issue with Google Apps Script deployed as a web app - google-apps-script

I am facing HTTP 401 errors while trying to call a deployed Apps Script (as a web app, accessible to "anyone") from a second GAS with UrlFetch and a bearer in authorization header. The scripts were working fine for months until around two weeks ago.
Here are two small scripts to reproduce the error.
Script A - Deployed as a web app, accessible to "Anyone".
function doGet(e) {
var params = e.parameter.params;
console.info("Parameters : " + JSON.stringify(e.parameter));
return ContentService.createTextOutput("Success");
}
Script B - Calling the script A via UrlFetch
function callURL() {
var param = {
method : "get",
headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
followRedirects : true,
muteHttpExceptions:true,
};
var url = "https://script.google.com/macros/s/<script_A_deployed_url>/exec?param1=test";
var resp = UrlFetchApp.fetch(url,param);
if(resp.getContentText() != "Success"){
console.info(resp.getContentText());
throw resp.getContentText();
}
}

Can you confirm the following points again?
For the client side, are there some functions except for your script of the client side in the project? If there is only the script in the project, the scope for accessing to Web Apps is not enough. The scopes for Drive API are required to be included in the scope of access token.
You can see the current scopes at File -> Project properties -> Scopes.
For example, those are the following scopes.
https://www.googleapis.com/auth/drive.readonly
https://www.googleapis.com/auth/drive.file
https://www.googleapis.com/auth/drive
In my log, when Who has access to the app: is installed as Anyone, I confirmed that from April 11, 2018, it is required to be shared the project to access to Web Apps. This might be due to the update of Google.
Please share the project of Web Apps with users and try again.
For the Web Apps server side, if you set User accessing the web app for Execute the app as:, please authorize the scopes using own browser. This authorization is required to do only one time.
If these were not useful for your situation, I'm sorry.

Tanaike pointed me in the right direction. Apparently, some internal rules recently changed in the authentication mechanism for Apps Script deployed as a web app.
For B script, the default scope with UrlFetch is https://www.googleapis.com/auth/script.external_request, but it looks like we now need at least read access to A script, which means we also need Drive scopes.
In order to achieve that, you can for example have this function in B script to authorize them.
function setScope() {
DriveApp.getRootFolder();
}

Related

How to add authentication to a Google apps script without it expiring?

I have a Google apps script project which I use as a web application to save some data gathered from a web page (JavaScript only) to My Google sheets (think of it as a simple database for just me).
It's no need to use auth for anyone else other than my account, because, I don't use their accounts/data for anything at all. I just need to use my account only, so, when I deploy it, I make it execute as me, and accessible to anyone:
.
When I click deploy after previous screen, it asks for my permission (the consent dialogue) to access my account's data, which I grant it, and after that everything is good, and the HTTP requests to this script works just fine.
.
The problem is:
This authentication expires after maybe 7 days (I'm not sure) and the script stops working, I discover that when the HTTP requests to it return error 403
To fix that and make it work again, I need to run the script again from the Google apps script editor which asks for the permissions (consent) again:
.
I can't use it like that and the web page stop working when the authentication gets revoked!
I'm not publishing the script (I don't want/don't need to). Do I?
My question is, how can I add the authentication in a way that makes it lasts and stops asking me for it over and over again?
The script on Google apps script works like this:
function doPost(request) {
return checkRequest(request);
}
function checkRequest(request) {
//check the request & save the sent data to a google sheet here;
//...
return sendResponse({
success: true,
data: {result: 'Saved the data!' }
});
}
function sendResponse(response) {
return ContentService
.createTextOutput(JSON.stringify(response))
.setMimeType(ContentService.MimeType.JSON);
}
And I call it from the web page using Ajax HTTP POST request, like this:
jQuery.ajax({
url: 'https://script.google.com/macros/s/{script-id}/exec',
method: 'POST',
dataType: "json",
data: {key: 'value'},
success: function (response) {
console.log(response);
},
error: function (response) {
console.error(response);
}
});
And this is the response the script returns after few days when the authentication expires:
This has been reported to Google
There is already a report on Google's Issue Tracker which detail the same kind of behaviour:
Random Deauthorizations for script since Editor Update with no changes to code
Google does seem to know about this issue. From the issue tracker link, a response was given:
[...] reviewing the documentation, it reads:
Authorizations by a test user will expire seven days from the time of consent.
Source
So I'm not sure we should expect anything different from these tests.
Also re-reading this thread, in your first comment you said that this was affecting projects that already published. Though I understand that you fixed the original projects that were having issues by un-linking the GCP projects.
A possible fix would be filling the scopes manually in the manifest for these types of issues. This is because Apps Script tries to infer the scopes needed at runtime, and sometimes this can result in Apps Script trying to gain more permissive scope than what is authorized.
See Setting Explicit Scopes
However, token expiry in around 7 days is to be expected for projects in "testing" state.
Which seems to be the case for the users in the report thread.
There are multiple reasons that this expiration may occur, as explained in the Using OAuth 2.0 to Access Google APIs page.
That being said, if it's causing problems you can file your own bug about it here in the Google Apps Script component.
References:
Random Deauthorizations for script since Editor Update with no changes to code
Using OAuth 2.0 to Access Google APIs | Google Identity
Setting up your OAuth consent screen - Google Cloud Platform Console Help
Authorization Scopes - Setting explicit scopes | Apps Script | Google Developers

Which are the right scopes to run request on Apps Script API

I'm using Apps Script API to run a function with the service account's credential.
I added all scopes required in Rest resource API https://developers.google.com/apps-script/api/reference/rest/v1/scripts/run.
But when i run this script below it failed.
function run(){
var CREDENTIALS = {
"private_key": "Your Private key",
"client_email": "Your Client email",
"client_id": "Your Client ID",
"user_email": "Your Email address",
"api_key": "Your API key"
};
var service = getService(CREDENTIALS.client_email,CREDENTIALS.private_key);
service.reset();
if (service.hasAccess()) {
var url = 'https://script.googleapis.com/v1/projects/[SCRIPT ID]:run';
var body = {
"function": [FUNCTION NAME]
};
var params = {
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
},
method: 'post',
playload : JSON.stringify(body),
contentType: 'application/json',
muteHttpExceptions: true
};
var response = UrlFetchApp.fetch(url, params);
Logger.log(response);
}
else {
Logger.log(service.getLastError());
}
}
function getService(email, privateKey) {
return OAuth2.createService('Service Account')
// Set the endpoint URL.
.setTokenUrl('https://oauth2.googleapis.com/token')
// Set the private key and issuer.
.setPrivateKey(privateKey)
.setIssuer(email)
// Set the name of the user to impersonate. This will only work for
// Google Apps for Work/EDU accounts whose admin has setup domain-wide
// delegation:
// https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority
.setSubject([USER EMAIL])
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scope. This must match one of the scopes configured during the
// setup of domain-wide delegation.
.setScope('https://www.googleapis.com/auth/script.external_request');
}
I've got a 404 Error and I think it comes from the scopes list.
So I can't run a script deployed as an API Executable with the OAuth2.0 token.
Which scopes should I choose to run a function via an HTTP request?
In your run function, for the params object you should have payload not playload.
You want to use Apps Script API with the service account.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Issue and workaround:
Unfortunately, in the current stage, the method of scripts.run in Apps Script API cannot be used with the service account. The official document says as follows. About this, when I tested this, I could confirm that the method of scripts.run in Apps Script API cannot be used with the service account.
Warning: The Apps Script API doesn't work with service accounts.
From above situation, as the workaround, how about using the access token retrieved by OAuth2? In order to use the Apps Script API with OAuth2, it is required to link Cloud Platform Project to Google Apps Script Project. About this, you can see the flow for linking them at here.
Note:
I think that when you use OAuth2, Oleg Valter's comment and TheAddonDepot's answer are very useful.
References:
Executing Functions using the Apps Script API
Linking Cloud Platform Project to Google Apps Script Project
If this was not the direction you want, I apologize.
Added:
You want to make several users run the script as the owner who is you.
From your replying, I could understand like above. When Apps Script API is used for above situation, the credential information is required to give each user. When each user uses the access token retrieved by your credential information, your goal can be achieve. But I cannot recommend this. So in your case, I would like to use Web Apps to achieve your goal. The flow is as follows.
1. Prepare script.
Please prepare your script. For example, in the current stage, you want to make users run a function of myFunction(), please put the following sample script.
function doGet(e) {
var values = e; // When you want to give the values by requesting, you can use the event object like "e".
var res = myFunction(values);
return ContentService.createTextOutput(res);
}
In this case, the GET method is used. When you want to only run the function, you can use this script. When you want to run the function by giving the large data, you can use doPost() instead of doGet().
2. Deploy Web Apps.
On the script editor, Open a dialog box by "Publish" -> "Deploy as web app".
Select "Me" for "Execute the app as:".
By this, the script is run as the owner.
Here, when "Anyone" is set, the script is run as each user. In this case, it is required to share the script to each user. And the access token is required to be used. Please be careful this.
Select "Anyone, even anonymous" for "Who has access to the app:".
In this case, no access token is required to be request. I think that as the test case, I recommend this setting.
Of course, you can also use the access token. At that time, please set this to "Anyone".
Click "Deploy" button as new "Project version".
Automatically open a dialog box of "Authorization required".
Click "Review Permissions".
Select own account.
Click "Advanced" at "This app isn't verified".
Click "Go to ### project name ###(unsafe)"
Click "Allow" button.
Click "OK".
Copy the URL of Web Apps. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please redeploy as new version. By this, the modified script is reflected to Web Apps. Please be careful this.
3. Run the function using Web Apps.
This is a sample curl command for executing myFunction with Web Apps. Please set your Web Apps URL. At above settings of Web Apps, each user can access by the following curl command.
curl -GL \
-d "key=value" \
"https://script.google.com/macros/s/###/exec"
When key=value is used as the query parameter like above, at doGet(e), you can retrieve value using e.parameter.key.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
You can deploy the script as a Web App. To do so, go to Publish > Deploy as web app. Set the Execute the app as: field to Me (youremail). This way you can share the script as a browser link, and any user will run the script with your credentials.
You can add a some user interface with a confirmation message so the users know they have successfully executed the script. You can find the documentation in this link.

How to pass You need permission when access webapp be create by google app script

I have two Google Apps Scripts like this:
Script A:
This is a webapp. I use it like a web api. Depend on parameter I will return data what I read from a SpreadSheet only I can access and I will add or edit data in this SpreadSheet.
I published it with following options:
Execute as me
Who has access to the web app:Anyone
Script B:(in a SpreadSheet)
I use this SpreadSheet like a input form. And I will request to script A by UrlFetchApp function.
The problem:
When I use UrlFetchApp function, the response content is a html like following image. I think this is a request access dialog what will send a request mail to script A owner. I tried to share script B project to test user. It work fine. But I want to show html dialog and do something for test user can send mail normally.
Questions:
How can I show this html content on a dialog in SpreadSheet B?
If have another way which I must not share script A project to other one, tell me please! Because they can see my script A when I shared it to them.
I have used ScriptApp.getOAuthToken to verified webapp like this:
function requestAPI(request_parameter_string) {
var param = {
method : "get",
headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
followRedirects : true,
muteHttpExceptions:true
};
Logger.log("run to this");
var url = "https://script.google.com/macros/s/" + api_id + "/exec?" + request_parameter_string;
var response = UrlFetchApp.fetch(url,param);
if(response.getContentText() != "Success"){
return response.getContentText();
}
}
Your situation is as follows.
Web Apps is deployed as Execute as me snd Who has access to the web app:Anyone.
You want to make users access to your Web Apps using the access token.
If my understanding is correct, how about this modification?
Modification points:
If you access to Web Apps that you are owner, you can access using the access token.
If other users access to your Web Apps, when the project file which deployed Web Apps is shared to users, users can access using the access token.
When the file is not shared, such error occurs.
If you use the container-bound script, please share it.
If you use the standalone script, please share it.
For the access token, https://www.googleapis.com/auth/drive is required to be included in the scopes.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script

Using AuthToken obtained via ScriptApp.getAuthToken() to call web apps in GAS

I created 2 simple standalone scripts to test the authorization workflow. The first script is a web app that is accessible only to me.
function doGet(e) {
return ContentService.createTextOutput(JSON.stringify({"message":"works!"}))
.setMimeType(ContentService.MimeType.JSON);
}
The calling script gets the token via ScriptApp.getAuthToken() and makes a 'GET' request to the web app.
function call() {
var token = ScriptApp.getOAuthToken();
var header = {"Authorization":"Bearer " + token};
var options = {
"method":"GET",
"headers": header,
"muteHttpExceptions": true
};
var url = 'APP_URL';
var response =UrlFetchApp.fetch(url, options);
Logger.log(response.getResponseCode()); //returns 401
Logger.log(response.getContentText()); // returns 'Unauthorized'
}
Unfortunately, it doesn't seem to work as I get the 'Unauthorized' response. My initial thought was that the token is scoped to each individual script, but GAS documenation indicates the contrary, stating that the ScriptApp token would be sufficient in month cases.
https://developers.google.com/apps-script/reference/script/script-app#getOAuthToken()
I would appreciate any help.
If you are still looking for this answer, how about this answer? I think that when the scopes are installed by Manifests, you can access the Web Apps using the access token with the scopes.
Deploy Web Apps :
The condition for deploying Web Apps is as follows.
On script editor on the project with doGet().
Publish -> Deploy as web app
For "Execute the app as:", set "Me".
For "Who has access to the app:", set "Only myself".
At above condition, when "headers": {"Authorization":"Bearer " + token} is not used for option, the error occurs. So in order to access to Web Apps with above condition, please add the following 2 scopes. In your case, the following scopes are required to authorize.
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/script.external_request
In your case, the above 2 scopes are required. In the case of only https://www.googleapis.com/auth/script.external_request, the error of Unauthorized occurs.
Add scopes to Manifests :
Please install above scopes to Manifests (appsscript.json) as follows.
On script editor on the project with call().
View -> Show manifest file
Please add the following oauthScopes to appsscript.json.
"oauthScopes": ["https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/drive"]
Response :
After it installed above, please try to run your call() again. In my environment, I retrieved the following response.
200.0
{"message":"works!"}
If I misunderstand your question, I'm sorry.

Set email signature using Google Apps Script with Gmail API

I'm trying to set the Gmail signature of the user executing the script (Execute the app as: "User accessing the web app"; Who has access to the app: "Anyone within my domain") using the following function:
function setSignature(signature) {
var newSig = Gmail.newSendAs();
newSig.signature = signature;
Gmail.Users.Settings.SendAs.patch(newSig, "me", Session.getActiveUser().getEmail());
}
where signature is some html. This function is called from a client-side script when a form is submitted:
google.script.run.withSuccessHandler(signatureSuccess).setSignature($("#signatureParent").html());
The user is served a web app using the HtmlService containing the form. The Gmail API has been enabled in both the Advanced Google Services window as well as the Google API Console.
My issue is that when the I try and execute the function I receive the following console error message:
The message states that the auth scope gmail.settings.basic is missing. This is despite the user authorizing the web app before any html is served:
How do I fix or work around this issue?? The strange thing is I've had this working previously so I don't know what I'm doing wrong.
EDIT:
I've noticed that if I create a simple Apps Script with just the function:
function testSet() {
var testSig = "signature";
var newSig = Gmail.newSendAs();
newSig.signature = testSig;
Gmail.Users.Settings.SendAs.patch(newSig, "me", Session.getActiveUser().getEmail());
}
And leave out everything else I get presented with these permissions to authorize:
If I click Allow it works! So clearly "Manage your basic mail settings" a.k.a. auth scope gmail.settings.basic is required and isn't being asked for in the more involved script.
So how do I force that permission to be acquired or how do I rewrite my script to get the correct set of permissions needed?
After extensive testing I've determined that this issue is a bug in Google Apps Script in determining what scopes are required.
A basic version of my script requires these scopes (File > Project Properties > Scopes):
Extending the script to interact with Google Drive modifies the scopes to this:
By dropping the required gmail.settings.basic scope a critical function within the script is denied permission to run. Infuriating.
I was also facing the same issue on nodejs application, the solution is to generate referesh token using this required scope which is mentioned in the rest api documentation find below.
rest apis documentation
you can create refresh token using required scopes on this link if you're logged in developer account.
https://developers.google.com/oauthplayground: