Apps Script example · 10 min read

User Auth Web App: Copy-Paste Apps Script Pattern

Working user auth web app example in Apps Script—copy-paste code, common mistakes, and when to get it built professionally.

HtmlServiceWeb appSession

Apps Script web apps can run as the accessing user so Session.getActiveUser().getEmail() returns a real identity.

This pattern looks up the email on a Users sheet for role and display name, then fills an HtmlService template.

Deploy with Execute as: User accessing the web app. If you execute as you (developer), getActiveUser often returns empty for visitors.

getServerTime shows a google.script.run endpoint that still sees the same active user after load.

Need this built? Hire a Google Apps Script developer →

Sheet / project setup

Sheet / FileFieldPurpose
UsersA EmailLogin key
UsersB NameDisplay name
UsersC Roleviewer / editor / admin
Index.htmltemplateUses <?= email ?> etc.
DeployExecute as userRequired for getActiveUser

What this script does

doGet() reads the active user email, loads profile defaults, and evaluates Index.html with template fields.

Unknown emails still enter as role viewer so first-time users are not hard-blocked.

Prerequisites

HtmlService file Index.html and a Users sheet; web app deployed to users with Google accounts.

  • Execute as: User accessing
  • Access: your domain or anyone with Google
  • Users sheet maintained

Walkthrough

Deploy, open the URL while signed in, confirm the email renders. Call getServerTime from client JS to verify identity on server calls.

Edge cases

Incognito without Google sign-in yields empty email — show a sign-in message as in the sample.

  • getEffectiveUser differs when execute-as is developer
  • Template escaping: use <?!= for HTML, <?= for escaped text

How to test

Add yourself to Users as admin; open as another account that is only viewer; confirm role text changes.

Hardening for production

Do not trust client-passed emails — always re-read Session on the server for mutations.

Variations

Combine with Admin Directory to auto-provision names from Workspace.

Full code: doGet() + getServerTime()

Add Index.html, deploy as a web app executing as the accessing user, then open the /exec URL signed in.

/**
 * Web app that shows different HTML based on the signed-in Google user.
 * Deploy: Execute as User accessing / Who has access: domain or anyone with Google.
 */
function doGet() {
  const email = Session.getActiveUser().getEmail();
  if (!email) {
    return HtmlService.createHtmlOutput(
      "<p>Sign in with Google to continue.</p>");
  }

  const profile = lookupProfile_(email);
  const template = HtmlService.createTemplateFromFile("Index");
  template.email = email;
  template.role = profile.role;
  template.displayName = profile.name;
  return template.evaluate()
    .setTitle("Workspace Portal")
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

function lookupProfile_(email) {
  const sheet = SpreadsheetApp.getActive().getSheetByName("Users");
  const data = sheet.getDataRange().getValues();
  for (let i = 1; i < data.length; i++) {
    if (String(data[i][0]).toLowerCase() === email.toLowerCase()) {
      return { email: data[i][0], name: data[i][1], role: data[i][2] };
    }
  }
  return { email: email, name: email.split("@")[0], role: "viewer" };
}

function getServerTime() {
  // callable from google.script.run after auth
  return {
    email: Session.getActiveUser().getEmail(),
    now: new Date().toISOString(),
  };
}
  1. Line 6: Returns the visitor when Execute as is User accessing.
  2. Line 13: Loads Index.html as a template.
  3. Line 12: Maps email to role from the Users sheet.
  4. Line 30: Default for emails not yet provisioned.
  5. Line 17: Renders HTML for the response.
  6. Line 33: Example google.script.run server function.
  7. Line 37: Returns a timezone-safe timestamp to the client.

Deploy this example

  1. 01

    Open Apps Script

    In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.

  2. 02

    Paste and save

    Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.

  3. 03

    Authorize once

    Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.

  4. 04

    Add the trigger

    Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.

Before you run: auth web app

  • 1Index.html exists in the project
  • 2Users sheet has Email / Name / Role headers
  • 3Deployment Execute as = User accessing the web app
  • 4Test with two Google accounts
  • 5Never authorize sensitive writes based on client input alone
  • 6Confirm empty-email UX for signed-out visitors

Frequently asked questions

Usually the deployment executes as the developer. Switch to User accessing the web app and create a new version.

Effective user is whose authority runs the script. Active user is who is signed in. For user-auth UX you want active under execute-as-user.

Prefer getActiveUser().getEmail(). Older Session helpers are less explicit about active vs effective.

Yes if access is Anyone with Google Account. Domain-only deployments exclude consumer accounts.

Re-check role from Users (or Directory) inside every mutating server function.

Yes via separate OAuth2 library flows; this example only covers Google sign-in identity.

Only if embedding in an iframe. Tighten for standalone portals.

Related examples

Want this wired into your real workflow?

I adapt these patterns to your Sheet structure, APIs, and triggers — deployed in your Google account. Fixed-scope quotes from $500 · free 30-min consult · quote within 24 hours.