0

I have the same problem as described here: Google Web Apps - Get user email but run scripts as owner

The given solution is to create a client script that runs as the current user and a middleware script that runs as the owner. The client script then calls the middleware script with UrlFetchApp.fetch(...) and the middleware handles the request in the doGet(e) function. This works fine in a test environment.

But to have it productive, I need to have the middleware script only accessible by domain users. Than, the UrlFetchApp.fetch call returns a login dialog.

So, is there a way to call a Google App Script by URL without manual log in?

This is my frontend script that runs with the user's privileges:

function doGet(e) {
  var data = HtmlService.createHtmlOutput(backendCall());
  // TODO: Format data as HTML
  return HtmlService.createHtmlOutput(data);
}

function backendCall() {
  const url = `${backendUrl}?user=${encodeURI(Session.getEffectiveUser().getEmail())}`;

  const options = {
      method: 'GET',
      muteHttpExceptions: true,
      headers: { 
        Authorization: `Bearer ${ScriptApp.getOAuthToken()}`,
        Accept: 'application/json'
      }
  };

  return UrlFetchApp.fetch(url, options).getContentText();
}

And the backend script that runs with the owner's privileges:

function doGet(e) {
    return ContentService.createTextOutput(getUserData(e.parameter.user)).setMimeType(ContentService.MimeType.JSON);
}

function getUserData(user) {
  const sheet = SpreadsheetApp.openById(sheetId).getActiveSheet();
  const data = sheet.getDataRange().getValues() // Get whole sheet data. This is much faster than getting data row by row.
      .filter((row) => row[cols.user] === user) // Select data for selected user.
      .map(formatRow)                           // Format sheet row to JavaScript objects.
      .sort((a, b) => a.day - b.day);           // Sort by date.
  return JSON.stringify(data);                  // Return as JSON string.
}

The result is, that the backend script returns not the data, but the error message "Error 401 Unauthorized".

7
  • Can you elaborate on what exactly you're trying to do step by step? If the users are running a script that uses UrlFetchApp.fetch() then the login request is unavoidable because this method uses a scope that requires authorization.
    – Daniel
    Commented Jan 9, 2023 at 18:08
  • More details would be appreciated indeed. For what I understand so far, if your webapp is deployed only for domain, you can use Urlfetch adding ScriptApp.getAuthToken() in headers AND the project file must be shared with domain too (at least as viewer)
    – Waxim Corp
    Commented Jan 9, 2023 at 19:17
  • @WaximCorp What do you mean with "the project file" and "shared with domain"? Of course I deployed both projects.
    – Mr. Clear
    Commented Jan 16, 2023 at 9:01
  • The project file, which contains the code, must be shared (not the same as deployed), at least as viewer. Tell me if this is the same problem : stackoverflow.com/questions/75020770/…
    – Waxim Corp
    Commented Jan 16, 2023 at 9:29
  • When I share the project, nothing changes. Question 75020770 seems to be related but the problem is not very specific there so I cannot tell if it's the same problem. But based on it I adopted my code and now I get a 401 instead of an login dialog.
    – Mr. Clear
    Commented Jan 16, 2023 at 10:48

1 Answer 1

1

I copied your codes and made some modifications in order to make it work :

For both projects :

  • Go to Setup, and check display manifest "appsscript.json" to ON
  • Modify your appscript.json with oauthScopes like following :
{
  "timeZone": "Europe/Paris",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "webapp": {
    "executeAs": "USER_DEPLOYING",
    "access": "DOMAIN"
  },
  "oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/userinfo.email"
  ]
}

Your front-end project :

const backendUrl = "https://script.google.com/.../exec";

function doGet(e) {
  return ContentService.createTextOutput(backendCall());
  //instead of : HtmlService.createHtmlOutput
}

function backendCall() {
  const url = `${backendUrl}?user=${encodeURI(Session.getEffectiveUser().getEmail())}`;

  const options = {
      method: 'GET',
      muteHttpExceptions: true,
      headers: { 
        Authorization: `Bearer ${ScriptApp.getOAuthToken()}`
      }
  };

  return UrlFetchApp.fetch(url, options).getContentText();
}

Back-end project:

function doGet(e) {
    return ContentService.createTextOutput(getUserData(e.parameter.user)).setMimeType(ContentService.MimeType.JSON);
}

function getUserData(user) {
  const sheet = SpreadsheetApp.openById([ID_SPREADSHEET]).getSheetByName([SHEETNAME]); 
  //instead of  getActiveSheet()

  const data = sheet.getDataRange().getValues() 
               .filter((row) => row[cols.user] === user)
               .map(formatRow)                          
               .sort((a, b) => a.day - b.day);           

  return JSON.stringify(data);
}
1
  • Thanks, now it works. I think the "oauthScopes" was what was missing.
    – Mr. Clear
    Commented Jan 16, 2023 at 21:14

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.