I am attempting to remotely execute a google apps script function using the apps script api through a postman request.
I first get credentials from the Google API Playground
Then they are inputted into postman as OAuth2 credentials, and inputted in the headers like this:
Then the appropriate link is requested:
Then the body prepared:
And then the response after making the request:
Postman is returning an error 403, which, according to Google's documentation
indicates that "the Cloud Platform project used to authorize the request is not the same as the one used by the script." An error 403 is an authorization error and can mean many things, but let's assume that the error is what google proclaims it to be.
I have full control over the GCP project utilized by my script, but I'm not aware of where to find the project that was used to authorize the request.
Where might I gain access to this GCP project so I can assign my script to this project, thereby eliminating the 403 error?
Thanks!
To use OAuth Access Tokens from the OAuth Playground with Apps Script, you need to specify the correct Client ID and Client Secret from the same project.
In the OAuth Playground. Click on the gear icon (top right). Select "Use your own OAuth credentials". Then enter the Client ID and Secret ID created in the same project as Apps Script.
To query a Google URL using a valid Access token with Postman you can log the access token from apps script and use it after.
In your apps script after validating scope, i.e. running a first time the script, log the token :
function logToken(){
Logger.log(ScriptApp.getOAuthToken());
}
Then in Postman query the Google URL by setting in the header the access token :
"Authorization": "Bearer THE_ACCESS_TOKEN"
Security warning : for security reason I have to tell you that an access token is valid 1 hour so technically if you grant full drive scope to your app with this access token we can browse all your Drive.
Related
I'm attempting to make a post request from an Airtable script to Google Apps Script. I have a doPost() function set up in the Google Apps Script file, but the post request needs to be authenticated to run.
I believe I need to pass an OAuth token in the header of my request, but I'm unsure how to get this token in the first place. I've found this doc here about web apps in Google Apps Script but no luck finding how to generate an OAuth token from an external service.
I may be completely off the rails with my thinking, so if there's an easier way to make a post request and authenticate it from an external service, I'm all ears.
EDIT:
I don't really have any code at the moment. I'm using Postman to send the calls to Google to test. In Apps Script I have the following inside just to test.
doPost(e) { Logger.log("POST REQUEST") }
When I make the call from Postman, the function isn't triggered. In short, I know I need an authorization in the post request, but I don't know how or where to get it.
How can I securely call a Google Cloud Function via a Google Apps Script?
✅ I have a Google Cloud Function, which I can access at https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION, and which I would like to allow certain users to invoke via an Apps Script.
✅ To secure the Cloud Function, I have set Cloud Function Invoker to only include known email (e.g. USER#COMPANY.com, where this is a valid Google email).
✅ I am able to successfully invoke the Cloud Function via curl, while logged into gcloud with this email, by running: curl https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION -H "Authorization: Bearer $(gcloud auth print-identity-token)".
✅ I have granted the following oauthScopes in my Apps Script's manifest:
"https://www.googleapis.com/auth/script.external_request"
"https://www.googleapis.com/auth/userinfo.email"
"https://www.googleapis.com/auth/cloud-platform"
⛔️ However, when I attempt to invoke the Cloud Function via a Google Apps Script, while logged in with the email USER#COMPANY.com, I am unable to invoke it and instead returned a 401. Here is how I have attempted to invoke the Cloud Function:
const token = ScriptApp.getIdentityToken();
const options = {
headers: {'Authorization': 'Bearer ' + token}
}
UrlFetchApp.fetch("https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION", options);
ℹ️ I have also tried the following:
Using ScriptApp.getOAuthToken()
Adding additional oauthScopes, e.g. openid.
Creating an OAuth Client ID with https://script.google.com set as an Authorized Javascript origin.
Deploying the Apps Script.
Crying out to the sky in utter, abject despair
I struggled very much authenticating from Apps Script to invoke a Cloud Run application and just figured it out, and I believe it's similar for calling any Google Cloud application including Cloud Functions. Essentially the goal is to invoke an HTTP method protected by Google Cloud IAM using the authentication information you already have running Apps Script as the user.
The missing step I believe is that the technique you're using will only work if the Apps Script script and Google Cloud Function (or Run container in my case) are in the same GCP project. (See how to associate the script with the GCP project.)
Setting it up this way is much simpler than otherwise: when you associate the script with a GCP project, this automatically creates an OAuth Client ID configuration to the project, and Apps Script's getIdentityToken function returns an identity token that is only valid for that client ID (it's coded into the aud field field of the token). If you wanted an identity token that works for another project, you'd need to get one another way.
If you are able to put the script and GCP function or app in the same GCP project, you'll also have to do these things, many of which you already did:
Successfully test authentication of your cloud function via curl https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION -H "Authorization: Bearer $(gcloud auth print-identity-token)" (as instructed here). If this fails then you have a different problem than is asked in this Stack Overflow question, so I'm omitting troubleshooting steps for this.
Ensure you are actually who the script is running as. You cannot get an identity token from custom function in a spreadsheet as they run anonymously. In other cases, the Apps Script code may be running as someone else, such as certain triggers.
Redeploy the Cloud Function as mentioned here (or similarly redeploy the Cloud Run container as mentioned here) so the app will pick up any new Client ID configuration. This is required after any new Client ID is created, including the one created automatically by adding or re-adding the script to the GCP project. (If you move the script to another GCP project and then move it back again, it seems to create another Client ID rather than reuse the old one and the old one will stop working.)
Add the "openid" scope (and all other needed scopes, such as https://www.googleapis.com/auth/script.external_request) explicitly in the manifest. getIdentityToken() will return null without the openid scope which can cause this error. Note to readers: read this bullet point carefully - the scope name is literally just "openid" - it's not a URL like the other scopes.
"oauthScopes": ["openid", "https://...", ...]
Use getIdentityToken() and do NOT use getOAuthToken(). According to what I've read, getOAuthToken() returns an access token rather than an identity token. Access tokens do not prove your identity; rather they just give prove authorization to access some resources.
If you are not able to add the script to the same project as the GCP application, I don't know what to do as I've never successfully tried it. Generally you're tasked with obtaining an OAuth identity token tied to one of your GCP client ids. I don't think one app (or GCP project) is supposed to be able to obtain an identity token for a different OAuth app (different GCP project). Anyway, it may still be possible. Google discusses OAuth authentication at a high level in their OpenID Connect docs. Perhaps an HTML service to do a regular Google sign-in flow with a web client, would work for user-present operations if you get the user to click the redirect link as Apps Script doesn't allow browser redirects. If you just need to protect your service from the public, perhaps you could try other authentication options that involve service accounts. (I haven't tried this either.) If the service just needs to know who the user is, perhaps you could parse the identity token and send the identifier of the user as part of the request. If the service needs to access their Google resources, then maybe you could have the user sign in to that app separately and use OAuth generally for long term access to their resources, using it as needed when called by Apps Script.
The answer above is very good. But since I am new with this I still had to spend a lot of time trying to figure it out.
This worked for me:
Apps Script code:
async function callCloudFunction() {
const token = ScriptApp.getIdentityToken();
const options = {
headers: {'Authorization': 'Bearer ' + token}
}
const data = JSON.parse(await UrlFetchApp.fetch("https://MY_REGION-MY_PROJECT.cloudfunctions.net/MY_FUNCTION", options).getContentText())
return data
}
Make sure that in project config you have the same project where your function is created.
After that, you can add the emails of the users you want to access the script on the Permission section in the function.
And as #alexander-taylor mentioned as well, make sure to add the scopes to your manifest file. You can make the manifest visible from the configuration tab in apps script. It took me some time to get that too.
Thanks to your comment you can do 2 things. But before, you have to know that you can't (or I least I never achieve this), create a valid identity token for being authenticated by Cloud Function and Cloud Run with a user credential. I opened a question on this
But you can call Google Cloud API with user credential! So
You can use the function test call API. The quotas limit you to 16 calls per 100 minutes (of course, it's design for test!)
You can publish a message into PubSub and plug your function on it. In this pattern your call is asynchronous.
We are looking for a way to trigger a function in the add-on in the user's context (authorization) from our web app. We tried using Apps Script Execution API to trigger that. The flow is add-on (apps script) registers the access token in our web app and the web app triggers execution API using the access token. This works for the first time. But the problem is, the access token is valid for only a few minutes. After which the web app is not able to initiate function through execution API due to authorization failure.
Questions:
Is our approach for triggering add-on/ apps script function from another web app in the user's context correct by using execution API?
If so, how do we avoid access token from expiring? Apparently, there is no API in apps script to 'get the refresh token'/ 'refresh the token'. How do we go about refreshing the token so that the token is valid forever (until the user cancels)?
Is there any radically different approach that will help in this case?
I see that add-ons like "PearDeck", "Form Approvals", "Form Publisher" etc are successfully doing this (triggering add-on function from the web app with user's authorization).
Hope to get some expert advice in this forum. Thanks
Used Execution API - with the problem in token getting expired
function prepareToken() {
// send to web app
var token = ScriptApp.getOAuthToken();
URLFetchApp.fetch("url?token="+token);
}
function doSomeWorkFromUserContext() {
// impl here
}
POST https://script.googleapis.com/v1/scripts/{scriptId}:run
Request Header
Authorization: Bearer + {token}
Request Body
{
"function": "doSomeWorkFromUserContext"
}
The function doSomeWorkFromUserContext should get called all the time of invocation but getting Authorization error after some time.
When a user authorizes your app for the first time you receive a refresh token along with the access token. The access token typically expires after one hour and you have to use the refresh token to fetch a new access token.
Note that the refresh token is only returned when the user initially authorizes your app.
You'll need to revoke the user's authorization (see below) and have them reauthorize the app to get the refresh token. Revoking access can be achieved in one of 3 ways:
Have the user uninstall the app
Use the ScriptApp.invalidateAuth() function
Make a HTTP GET request to the OAuth2 revoke endpoint with the users access token ("https://accounts.google.com/o/oauth2/revoke?token={token}")
Eric Koleda's GAS OAuth2 library does a great job of managing the entire OAuth2 flow (including automatically refreshing access tokens) so be sure to check that out.
I've written a Google Apps Script web app to allow users to change their gmail signature using a html form template via the Gmail API. This has been working for a few months but has suddenly stopped working. The console error I get is:
mae_html_user_bin_i18n_mae_html_user.js:40 Uncaught Error: Missing required scope "https://www.googleapis.com/auth/gmail.settings.basic" for modifying primary SendAs
at setSignature (Code:151) (Email Signature Generator:21)
This error refers to the use of this line of code:
Gmail.Users.Settings.SendAs.patch(newSig, "me", Session.getActiveUser().getEmail());
SendAs.patch requires the following authorization:
https://developers.google.com/gmail/api/v1/reference/users/settings/sendAs/patch
https://www.googleapis.com/auth/gmail.settings.basic
https://www.googleapis.com/auth/gmail.settings.sharing
After deleting the previously stored permissions and re-running the app the authorization window lists the following permissions:
Note that "Manage your basic email settings" (gmail.settings.basic) is missing. So, what am I doing wrong? Can I rectify this, or is this a gmail-api auth bug?
According to https://developers.google.com/apps-script/guides/services/authorization :
Apps Script determines the authorization scopes (like access your
Google Sheets files or Gmail) automatically, based on a scan of the
code. Code that is commented out can still generate an authorization
request. If a script needs authorization, you'll see one of the
authorization dialogs shown here when it is run.
Is there any way to manually request an auth permission?
use this scope:
https://mail.google.com/
to have full access. You shouldn't have problem with scopes/authorization with that.
I'd like to use a service account to access a Google Sheet via the Apps Script Execution API, but it's not clear from the documentation whether this is supported.
The steps I've tried (which result in a 403 status from the Execution API) are:
Create a new (unbound) Apps Script
Visit the linked Developer Console project
Enable the Execution API
Create a new service account within the same project (downloading
the generated JSON file)
Create a new Google Sheet and share it with the service account's
email address (this is the step I'm least sure about)
Write an apps script function that reads from the spreadsheet
Run the script manually from the Script Editor (to set the scopes
on the script correctly)
Publish the script ("Deploy as API executable"), making it accessible
to 'anyone'
Mint a new OAuth2 token using the service account and the scopes
linked to the script (in our case just
'https://www.googleapis.com/auth/spreadsheets')
Attempt to make a call to the Execution API using the token
This is the response I got:
{
"error": {
"code": 403,
"message": "The caller does not have permission",
"status": "PERMISSION_DENIED"
}
}
Does this not work because Service Accounts are never able to access the Execution API? Or is there something wrong with the steps above?
Your original 403 error indicates that you have incorrectly set up authentication for your service account. However, even if you get that working, as of now (10 Nov 2015) you cannot execute Apps Scripts via the Service Account.
It's a known bug, and is being tracked in the Apps Scripts Issue Tracker.
Currently(2020), Service accounts cannot work with Apps script API. As written in the documentation,
Warning: The Apps Script API does not work with service accounts.
Your problem is probably that the script is associated with the wrong project (i.e. its own project, instead of the project associated with your Service Account). Here is what you need to do:
From the Scripts editor select the following menu item: Resources > Developer Console Project.
On this screen enter the project number for your dev console.
cf this answer