By guillaume blaquiere.Jul 13, 2021
Google is a large company with a large number of applications that you can reach by APIs. To leverage and duplicate the power of each of these products, you can plug them together and create something bigger! However, it’s not so simple: each product is well documented but the authentication part to connect them is blurry, out of date or missing.
Generate Access Token
Thankfully, when you want to plug Google Workspace products and Google Cloud APIs, it’s quite easy with out-of-the-box methods.
When you use App Script, you have a single method to call to generate an access token (OAuth token)
ScriptApp.getOAuthToken();
Easy, no?
Google Cloud API calls
No, it’s not so simple… Have a try at a simple piece of code. Go to https://script.google.com/ and start a new project
Copy this code in your function
function myFunction() {var options = {
'method' : 'get',
'headers': {"Authorization":"Bearer "+ScriptApp.getOAuthToken()},
}var response = UrlFetchApp.fetch("https://cloudresourcemanager.googleapis.com/v1/projects/<PROJECT_ID>",options); Logger.log(response.getContentText());
}
Replace PROJECT_ID
with your Google Cloud project ID
Save and run your code. Accept the permissions and you should get a 403 error, because of insufficient scopes.
The scope issue
Obviously yes, you are in an App Script product and you request an API in another product, with another scope. You need to add the Cloud Platform scope to your app.
For that, go to Project Settings
, and click the box Show "appscript.json" manifest file in editor
Go back to the editor and a new file is present
Open it and add the Cloud Platform scope in it. Your file should look like to that
{
"timeZone": "Europe/Paris",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/cloud-platform"
]
}
And run again your script.
FAIL AGAIN!!!!
This time, you don’t have the permission scope to run the script in App Script, you only have the Cloud Platform scope, not the default one of App Script.
So, add it to the scopes like that
{
"timeZone": "Europe/Paris",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/script.external_request"
]
}
Finally run your script, accept the new permission and …
403 again, the API is not active on a project number!!!
Project number that you don’t know, of course, it’s not “so” simple!
Request the correct project in the API calls
Under the hood, App Script creates a project for you, without any Google Cloud APIs activated, because it’s not a Google Cloud project. So, you need to set your Project in the App Script configuration.
For that, go back to the setting page and click on Change Project
And fill in the field with the Project Number (not the project ID. You can find it on the home section on Google Cloud Console)
Go back to your script and run it.
BOOM, that works!!
Great, now we can call the Google Cloud APIs with an access token.
Let’s call Cloud Run services to create interaction between my Workspace Apps and dynamic custom backends.
Cloud Run authentication requirements
Unlike Google Cloud APIs that require an Access Token, Cloud Run services require an identity token (JWT) to be able to authenticate and authorize the requester.
It’s the same for Cloud Functions deployed securely; and for App Engine protected with IAP
So, don’t worry, we can use the built-in functions of App Script
ScriptApp.getIdentityToken()
Really so simple?
Of course, not. I already talked about this topic in this article, and in some others. Google Cloud Run authentication isn’t so simple, and complex if you use user credentials.
Service Account solution
The easy way is to use a service account.
And OF COURSE NOT A SERVICE ACOUNT KEY FILE!!
To secure a file in a App Script document seems difficult and it’s a bad practice to use service account key file, when we can avoid it. We will do differently.
I already described a not well known API in this article: Service Account Credentials API
And we will reuse it! We need
- A service account with the permission to invoke a Cloud Run service (roles/run.invoker)
- A Cloud Run service deployed securely (
--no-allow-unauthenticated
) - The Service Account Token Creator role granted on the App Script user (roles/iam.serviceAccountTokenCreator).
The required permissions aren’t set in the owner role, you need this additional role even if you are project owner
The first step is to request an identity token on the service account through the Service Account Credential API.
We perform this Google Cloud API call with the Access Token of the current token, as seen before
var options = {
'method' : 'post',
'headers': {"Authorization":"Bearer "+ScriptApp.getOAuthToken()},
'contentType': 'application/json',
'payload': '{"includeEmail":true,
"audience": "AUDIENCE"}'
}var response = UrlFetchApp.fetch("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/<SERVICE_ACCOUNT_EMAIL>:generateIdToken",options)Logger.log(JSON.parse(response.getContentText()).token);
The AUDIENCE
is the raw URL of the Cloud Run service
This part prints the identity token. Now, you have to call your Cloud Run service with this identity token in the authorization header
var options = {
'method' : 'get',
'headers': {"Authorization":"Bearer " +
JSON.parse(response.getContentText()).token},
}var response = UrlFetchApp.fetch("<CLOUD_RUN_URL>",options);Logger.log(response.getContentText());
Run your script and BOOM! You have it!
Consistent authentication
Finally, the communication between products is not so simple, but the authentication logic and mechanism are consistent between all the products. You need to use the correct type of token and use the correct scopes. Nothing is magic, and “so simple”.
I hope this article will simplify your multi-Google-products solution and let you make awesome low code Workspace apps and integration with Google Cloud!
The original article published on Medium.