appscript.dev
Automation Advanced

Add login and access control to a web app

Gate Northwind pages behind authentication — Workspace email allowlist or token.

Published Aug 8, 2025

Northwind runs an internal admin page as an Apps Script web app. Anyone with the deployment URL can open it, and a URL has a way of getting forwarded, bookmarked and pasted into chat threads. An internal tool that returns booking data and client details should not be one shared link away from the whole company.

This script gates the page behind an email allowlist. doGet checks who is asking before it serves anything — if the signed-in Workspace account is on the list it gets the admin page, and if not it gets a flat “Access denied”. It is the smallest honest piece of access control you can put in front of a web app.

What you’ll need

  • An Apps Script web app project with an Admin.html file — the page you want to protect.
  • The web app deployed as Execute as: Me and Who has access: Anyone within [your domain]. This is what makes Session.getActiveUser() return a real email — see “Watch out for”.
  • The Workspace email addresses that should be allowed in.

The script

// Workspace accounts allowed to open the admin page.
// Add or remove addresses here — this is the whole allowlist.
const ALLOWED_EMAILS = [
  '[email protected]',
  '[email protected]',
];

// The HTML file served to allowed users.
const ADMIN_PAGE = 'Admin';

/**
 * Web app entry point. Checks the signed-in user against the allowlist
 * and serves the admin page only to accounts that are on it.
 *
 * @param {Object} e  The event object passed to every web app request.
 * @return {HtmlOutput} The admin page, or an access-denied message.
 */
function doGet(e) {
  // 1. Identify the signed-in user. Empty means we cannot verify identity.
  const email = Session.getActiveUser().getEmail();

  // 2. No email, or an email not on the list — refuse the request.
  if (!email || !ALLOWED_EMAILS.includes(email)) {
    Logger.log('Access denied for: ' + (email || '(no identity)'));
    return HtmlService.createHtmlOutput('Access denied');
  }

  // 3. Allowed — serve the protected admin page.
  Logger.log('Access granted for: ' + email);
  return HtmlService.createHtmlOutputFromFile(ADMIN_PAGE);
}

How it works

  1. ALLOWED_EMAILS is the entire access policy — a plain array of Workspace addresses. Adding or removing someone is a one-line edit and a redeploy.
  2. doGet runs on every request to the web app. It calls Session.getActiveUser().getEmail() to find out who is asking.
  3. If that email is empty, or it is not in ALLOWED_EMAILS, the script logs the attempt and returns a plain “Access denied” page — the admin HTML is never even loaded.
  4. If the email is on the list, the script logs the grant and serves Admin.html via HtmlService.createHtmlOutputFromFile.
  5. The check happens before any page content is built, so an unauthorised request gets nothing useful back.

Example run

Signed-in accountOn allowlist?What they see
[email protected]YesThe admin page
[email protected]YesThe admin page
[email protected]No”Access denied”
External Gmail userNo (empty email)“Access denied”

The execution log records each attempt, so you can see who tried to reach the page and when.

Run it

A web app does not run on a trigger — it runs when someone opens its URL:

  1. In the Apps Script editor, choose Deploy > New deployment.
  2. Pick Web app as the type.
  3. Set Execute as to Me and Who has access to Anyone within [your domain].
  4. Click Deploy, approve the authorisation prompt, and share the resulting URL with the people on ALLOWED_EMAILS.
  5. After editing the allowlist, deploy a new version so the change goes live.

Watch out for

  • The deployment setting is what makes this work. Execute as: Me with Access: Anyone within your domain populates Session.getActiveUser(). Set Access: Anyone and external users get an empty email — you cannot enforce identity that way, and the allowlist becomes meaningless.
  • This blocks page access, not data access. If Admin.html calls google.script.run functions, each of those server functions runs with its own permissions — gate them too, or a determined user can call them directly.
  • The allowlist lives in code. Every change needs an edit and a new deployment. For a list that changes often, read the allowed emails from a Sheet or Script Properties instead.
  • Session.getActiveUser() only returns an email for users in the same Workspace domain as the script owner. It will not identify consumer Gmail accounts even when they are signed in.
  • “Access denied” is a dead end with no way back. Consider linking to a contact address so a legitimate user who is simply not on the list yet knows who to ask.

Related