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.htmlfile — 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
ALLOWED_EMAILSis the entire access policy — a plain array of Workspace addresses. Adding or removing someone is a one-line edit and a redeploy.doGetruns on every request to the web app. It callsSession.getActiveUser().getEmail()to find out who is asking.- 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. - If the email is on the list, the script logs the grant and serves
Admin.htmlviaHtmlService.createHtmlOutputFromFile. - The check happens before any page content is built, so an unauthorised request gets nothing useful back.
Example run
| Signed-in account | On allowlist? | What they see |
|---|---|---|
[email protected] | Yes | The admin page |
[email protected] | Yes | The admin page |
[email protected] | No | ”Access denied” |
| External Gmail user | No (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:
- In the Apps Script editor, choose Deploy > New deployment.
- Pick Web app as the type.
- Set Execute as to Me and Who has access to Anyone within [your domain].
- Click Deploy, approve the authorisation prompt, and share the resulting
URL with the people on
ALLOWED_EMAILS. - 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.htmlcallsgoogle.script.runfunctions, 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
Build a branded approval interface
Approve Northwind requests through a custom UI — clients click, decision is logged.
Updated Nov 8, 2025
Build an interactive quiz or assessment app
Run Northwind tests with scoring and feedback — questions in a Sheet, results in another.
Updated Nov 4, 2025
Build a multi-page web app with routing
Structure a real Northwind app across views — query-param routing, shared layout.
Updated Oct 31, 2025
Build a form-to-PDF web service
Convert Northwind form submissions to PDFs on the fly — POST in, PDF out.
Updated Oct 27, 2025
Build an expiring secure-download generator
Issue time-limited Northwind links via a web app — token in URL, server-side check.
Updated Oct 23, 2025