A role-based access control (RBAC) module for Google Apps Script web apps. It is:

  • Vendored – copied into your project and owned by you, not installed as a dependency.
  • Workspace-native – lives entirely inside Google Workspace.
  • Zero infrastructure – no GCP project, Firebase Auth or Identity Platform required.

† If you can use a real identity platform, use it

Identity Platform, Firebase Auth and Google Sign-In authenticate arbitrary external users in ways Apps Script’s Session cannot. This kit is for the constrained case where:

  1. the app is deployed inside a single Workspace domain
  2. the only identity signal you have is the one Google hands you
  3. you want clean and layered authorisation functionality

What this kit is

This kit is authorisation, not authentication. It cannot check who the user actually is and explicitly trusts Google’s answer to that question with Session.getActiveUser().getEmail(). From there, a clean layer is built on top: roles, a role hierarchy, server-side route guards, a client navigation guard and conditional rendering.

That trust has a hard limit, and where the limit falls is decided by how you deploy. Read the Security page before you ship anything. For this repo it is not optional.

Use this kit if…

  • Your app lives inside a single Google Workspace domain.
  • You want role-based access without standing up external auth infrastructure.
  • You’d rather own a small module you copy in and adapt than take on a dependency.

Don’t use it if…

  • You need to authenticate arbitrary external or consumer users against a verified identity. Apps Script’s Session cannot do that reliably under an “execute as me” deployment. Reach for Identity Platform, Firebase Auth or Google Sign-In.
  • You need real session tokens, password auth, MFA or audit-grade auth logging. This kit does not provide them, and nothing built on Apps Script alone can.

The shape of the kit

Browser ── api.*() ──▶ google.script.run ──▶ server function
                                                  │
                                          requireAuth('editor')   ← real security
                                                  │
                                          auth-adapter.js          ← routes auth mode
                                           ┌──────┴───────┐
                                   sheet-backed       domain / allowlist
                                           └──────┬───────┘
                                                  │
                                  Session.getActiveUser().getEmail()  ← trust anchor

The client navigation guard and conditional rendering both live in src/client/auth-api.html. It turns google.script.run into promise-returning api.*() calls and carries the AUTH_ERROR: protocol the server guards throw. The guard prefixes its message which the wrapper recognises. A denied call then reaches the user as a session prompt or a permission message, not a raw failure. Neither client-side guard is a security control.

Where next