0

Usecase

I am designing a web app that acts as a frontend around a Google Form. I have set up a service account that will perform API calls to collect form responses, and manage form responses. However, some functionality is missing in the Google Forms API, (e.g. managing form responses), so as a workaround, I would like to do it through Apps Script.

Ideal Authentication Flow

  1. I have users share the Google Form to the service account email (service-account@XXX.iam.gserviceaccount.com)
  2. I can now access their Google Form with the Google Forms API.
  3. I can also access their Google Form through App Script

Potential Solution #1

In Apps Script, you can share the project with a service account, and even assign it as the owner. enter image description here

However, I want to execute the Web App in the context of the service account. I am not sure how to do this.

enter image description here

Is it possible to deploy an Apps Script under a service account? It is not possible programmatically as far as I can tell. If this is not possible, is it possible to run certain commands in Apps Script as a service account?

Here is a snippet of what I want to run:

function doPost(e) {
  // This line requires access to the Google Form
  const form = FormApp.openById(e.parameter['urlId']);
  // This action is only possible in Apps Script, not through the Google Forms API
  const FormResponse = form.createResponse();
  const res = FormResponse.submit();
  const link = res.getEditResponseUrl();
}

The end goal is to be able to perform actions through Apps Script without user interaction. This would look like a POST request to the web app, doing the action I need.

Potential Solution #2

A comment here suggests this is possible:

the access token of the service account can be used for the authorization of Web Apps

So I tried setting the Apps Script to "Execute as User", and passing in the accessToken of my service account in the Bearer section. This returned a popup, which I cannot get past.

enter image description here enter image description here

My service account was initialized like this:

const authProvider = new auth.GoogleAuth({
  keyFilename: filePath,
  scopes: [
    'https://www.googleapis.com/auth/forms',
    'https://www.googleapis.com/auth/drive',
    // ...
  ],
});
const authClient = await authProvider.getClient();
  const url = 'https://script.google.com/macros/s/.../exec'
  const accessToken = (await authClient.getAccessToken()).token;
  const res = await fetch(url, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${accessToken}`,
    },
  });

And the Apps Script only requires these two scopes:

  "oauthScopes": [
    "https://www.googleapis.com/auth/forms",
    "https://www.googleapis.com/auth/drive"
  ]

Is there some way to grant the Apps Script permission to the Service Account?

Peter S
  • 827
  • 1
  • 8
  • 24

1 Answers1

0

I ended up doing everything from the context of a user account, and no longer used the service account. This solution may not work for everyone, as I had to create a special gmail account just for my script.

First, I authenticated my user account using OAUTH and set the type to "offline". I then saved the refresh token I received to a file. Then, I used to refresh token to sign in.


const authClient = new auth.OAuth2(
  process.env.OAUTH_CLIENT_ID,
  process.env.OAUTH_CLIENT_SECRET,
  'http://localhost:8000'
);
authClient.setCredentials({
  refresh_token: process.env.CLIENT_REFRESH_TOKEN,
});
client = forms({
  version: 'v1',
  auth: authClient,
});
  const accessToken = (await authClient.getAccessToken()).token;

const appscriptDeployment =
  'https://script.google.com/macros/s/AKfycbxe_6S9tzu6bRHE5vYQ4XhyVluotbWmz7mdCd-l8oTM3NfygzksbSziv1hJ8XxgCVr9/exec';

  const url = new URL(appscriptDeployment);
  url.searchParams.append('urlId', formId);

  const res = await fetch(url, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${accessToken}`,
    },
  });

Then in AppScript, I set the permissions like:

enter image description here

/*
  https://developers.google.com/apps-script/guides/web
  When a user visits an app or a program sends the app an HTTP GET request, Apps Script runs the function doGet(e).
*/

function doGet(e) {
  const urlId = e?.parameter['urlId'];
  const form = FormApp.openById(urlId);
  const FormResponse = form.createResponse();
  const res = FormResponse.submit();
  const id = res.getId();
  const timestamp_ms = res.getTimestamp().getTime();
  const link = res.getEditResponseUrl();
  return ContentService.createTextOutput(
    JSON.stringify({ link, id, timestamp_ms })
  );
}
Peter S
  • 827
  • 1
  • 8
  • 24