Everything the kit does is driven by one object, AUTH_CONFIG, defined in config.js. Rename config.example.js to config.js, set the values and keep config.js out of version control since it holds your sheet ID. Keep only one of the two files: Apps Script shares one global scope across the project, so two const AUTH_CONFIG declarations stop it loading. There is one source of truth for roles, the hierarchy and the fail-closed behaviour, so you change auth policy in one place.
Quick start
const AUTH_CONFIG = {
APP_NAME: 'My App',
AUTH_SHEET_ID: 'PASTE_YOUR_AUTH_SHEET_ID_HERE',
OWNER_EMAIL: null,
USE_DOMAIN_AUTH: false,
ROLE_RANKS: { viewer: 10, editor: 20, admin: 30 },
DEFAULT_REQUIRED_ROLE: 'viewer',
ALLOW_OPEN_ENROLMENT: false,
OPEN_ENROLMENT_ROLE: 'viewer',
ALLOWED_DOMAIN: null,
ADMIN_EMAILS: [],
EDITOR_EMAILS: [],
DOMAIN_DEFAULT_ROLE: 'viewer',
};
In sheet-backed mode the auth sheet itself is generated for you. In the Apps Script editor, run SETUP_createAuthSheet() once from the function dropdown: it creates a private spreadsheet in your Drive with the headers, a role dropdown per row, Active checkboxes and sample rows in place, then logs the new sheet’s ID. Paste that ID into AUTH_SHEET_ID and replace the sample rows with real users. The function is owner-guarded, so visitors cannot call it through google.script.run – see Setup and admin.
Fields
Identity
| Field | Type | Default | Notes |
|---|---|---|---|
APP_NAME | string | 'My App' | Shown in logs and on the generated auth sheet. No security weight. |
AUTH_SHEET_ID | string | placeholder | ID of the private auth sheet. Generate it with SETUP_createAuthSheet(), run from the editor. Sheet mode only. |
OWNER_EMAIL | string | null | null | Your email. Optional. The owner-guarded setup entry points (SETUP_*, DEBUG_*) refuse non-owners by comparing the active and effective user, which holds under executeAs: USER_DEPLOYING. Under USER_ACCESSING that comparison passes for every visitor, so set this to keep those entry points owner-only. null leaves the USER_DEPLOYING-only behaviour. |
Auth mode selection
| Field | Type | Default | Notes |
|---|---|---|---|
USE_DOMAIN_AUTH | boolean | false | Selects the auth mode. false is sheet-backed: a user’s role comes from their row in the auth sheet. true is domain / allowlist mode: the role is derived from the user’s email address via ADMIN_EMAILS, EDITOR_EMAILS and ALLOWED_DOMAIN, and no sheet is read. Your requireAuth() calls behave identically in either mode. |
Roles
| Field | Type | Default | Notes |
|---|---|---|---|
ROLE_RANKS | object | { viewer: 10, editor: 20, admin: 30 } | The role hierarchy: a map of role name to numeric privilege rank, where a higher rank satisfies any lower requirement (see the tip below). Adding a role is safe; renaming admin or editor is not, because the built-in guards use those names literally – see Renaming the roles. |
DEFAULT_REQUIRED_ROLE | string | 'viewer' | The role required when you call requireAuth() with no argument. |
The hierarchy is the rank, not the order
A user satisfies a required role if their role’s rank in
ROLE_RANKSis at or above the required role’s rank. Leave gaps (10, 20, 30) so you can slot a role in later without renumbering. The kit derives an orderedROLESlist from this map automatically for the sheet dropdown and validity checks, so there is still a single source of truth.
Fail-closed behaviour
| Field | Type | Default | Notes |
|---|---|---|---|
ALLOW_OPEN_ENROLMENT | boolean | false | When false, a user not in the sheet is denied. When true, an identified but unknown user is granted OPEN_ENROLMENT_ROLE. |
OPEN_ENROLMENT_ROLE | string | 'viewer' | The role handed out under open enrolment. Should be your lowest role. |
Read before enabling open enrolment
Open enrolment plus an unidentifiable visitor is the worst case. Understand the empty-email landmine before turning this on. The kit ships closed for a reason.
Domain / allowlist mode
Only used when USE_DOMAIN_AUTH is true.
| Field | Type | Default | Notes |
|---|---|---|---|
ALLOWED_DOMAIN | string | null | null | Restrict access to one Workspace domain, for example 'example.com'. null means no restriction, which is not recommended for authenticated roles. |
ADMIN_EMAILS | string[] | [] | Emails granted admin. Case-insensitive. |
EDITOR_EMAILS | string[] | [] | Emails granted editor. Case-insensitive. |
DOMAIN_DEFAULT_ROLE | string | null | 'viewer' | Role for an identified, in-domain user on none of the lists above. Set to null to deny them and fail closed. |
Renaming the roles
The kit does not care what the roles are called. To use member, manager, owner:
ROLE_RANKS: { member: 10, manager: 20, owner: 30 },
DEFAULT_REQUIRED_ROLE: 'member',
Then guard routes with the new names, for example requireAuth('manager'). The auth sheet’s role dropdown is a snapshot of the derived ROLES list from when the sheet was generated; it does not update itself – edit the Role column’s data-validation rule and stored roles to match. The client guard in auth-api.html reads the role order from the server (getUserInfo returns it), so there is no second copy to keep in sync.
One caveat: the built-in names admin and editor are load-bearing. The administrative guards call requireAuth('admin') literally (auth-setup.js, auth-scopes.js), so after a rename the admin endpoints refuse everyone – in either mode – until those literals match the new name. Domain mode is coupled more deeply still: resolveDomainRole_ maps the allowlists to literal 'admin' and 'editor', so renaming while USE_DOMAIN_AUTH is true fails every allowlisted user closed (“Configured role is not in AUTH_CONFIG.ROLES”) until auth-adapter.js is updated too. A stale literal denies, it never grants, so a botched rename locks people out rather than letting anyone in. See Extending for the full rename procedure.