PROBLEM
Unautorized 401 error when posting a request to Google App Script webapp, from my Google Cloud Console function with a service account.
OVERVIEW
Google App Script Webapp deployed with
- Execute as: "user acessing the webapp"
- Who has access: "anyone with a google account" (The option "anyone" is only available if Execute as = myself)
Let's assume a dummy doPost(e) function that returns "hello world!"
function doPost(e) {
return ContentService.createTextOutput("Hello world");
}
Google Cloud Console - Function
const axios = require('axios');
const { GoogleAuth } = require('google-auth-library');
exports.calendarWebhook = async (req, res) => {
try {
const webAppUrl = process.env.WEB_APP_URL; // Make sure this env var is set correctly
const auth = new GoogleAuth({
credentials: JSON.parse(process.env.SERVICE_ACCOUNT_KEY), // Ensure this is set correctly
scopes: ['https://www.googleapis.com/auth/script.projects'],
});
const client = await auth.getClient();
const accessToken = await client.getAccessToken();
const requestBody = {
channelId: req.header('X-Goog-Channel-ID'),
resourceId: req.header('X-Goog-Resource-ID'),
resourceState: req.header('X-Goog-Resource-State'),
userToken: req.header('X-Goog-Channel-Token'),
secret: process.env.WEB_APP_SECRET,
};
const response = await axios.post(webAppUrl, requestBody, {
headers: {
Authorization: `Bearer ${accessToken.token}`,
'Content-Type': 'application/json',
},
});
console.log('Response from web app:', response.data);
res.status(200).send('Notification processed');
} catch (error) {
console.error('Error in Cloud Function:', error);
if (error.response) {
console.error('Web app response:', error.response.data);
console.error('Web app status:', error.response.status);
}
res.status(500).send('Internal Server Error');
}
};
Logs:
- Error in Cloud Function: AxiosError: Request failed with status code 401 at settle
- Web app response:
<HTML> <HEAD> <TITLE>Unauthorized</TITLE> </HEAD> <BODY BGCOLOR="#FFFFFF" TEXT="#000000"> <!-- GSE Default Error --> <H1>Unauthorized</H1> <H2>Error 401</H2> </BODY> </HTML>
If I deploy my webapp with (Execute as: Me ; Who has access: anyone) the post request works (without any token nor anything). But I can't deploy the webapp like this.
How can I autorize the Google Cloud console to post to my webapp?!?!?
Thanks community
EDIT
- Outcome: improvement but not there yet!
- Summary: calling webapp through Postman post request: working with a token of a logged-in user, but still not working with the token of the service account.
TESTS
1. Service account adjustment
- Added scopes (..../auth/drive) when requesting token
- Shared the Google App Script (edit rights)
- Result: Error 500 (see picture below)
2. Test with Logged-in user token
- User has edit access to the script
- Got the token access for the logged in user
- Called my webapp via Postman
- Result: SUCCESS!! (<< This wasn't working before, seems that adding the "...auth/drive" scope fixed it)
3. Service Account full scope request
- Added all the scope required in mywebapp to the service account as well (not just the "...auth/drive") when requesting the token.
- Result: Error 500 (see picture below)
4. Webapp deployed with (Execute as Me + who has access = anyone). Tested with service account:
- Result: SUCCESS!! (but I can't deploy the webapp like this, as initially stated)
HYPOTHESIS:
#2 & #4 => indicates there are no error in the Cloud function nor the doPost().
#2 & #3 => as a hypothesis... a service account is NOT considered "Anyone with a google account" (the "who has access" config when deploying the webapp), and so, cannot call the webapp. This still sounds weird to me... I tried to compare both access token via "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=XXX". Here's what I got... not sure what's making them behave differently. ANY TIPS?!?!?!
Service Account
{
issued_to: "some long number X",
audience: "same long number X",
user_id: "same long number X",
scope: //same as logged in user, except it's missing "https://www.google.com/calendar/feeds" which i don't thing makes any impact for calling the webapp....
expires_in: 3395,
email: "[email protected]",
verified_email: true,
access_type: "online"
}
Logged in user
{
issued_to: "KeyXYZ.apps.googleusercontent.com",
audience: "KeyXYZ.apps.googleusercontent.com",
user_id: "some long number Y",
scope: same as Service account, except it includes "https://www.google.com/calendar/feeds"
expires_in: 3575,
email: "[email protected]",
verified_email: true,
access_type: "offline"
}

scopes = ["https://www.googleapis.com/auth/drive"]