okhp3-google-gis-client-auth
OverKill Hill P³ · overkillhill.com · github.com/OKHP3
Intent: Give a static-site agent a reliable GIS token lifecycle for Google
API read flows without inventing a backend or leaking a Client Secret.
Scope boundary
| In scope |
Out of scope |
| GIS popup token model and GCP Web Application setup |
Authorization code flow, refresh tokens, or offline access |
React/TypeScript useGoogleAuth lifecycle |
Service accounts, non-Google identity providers, or multi-tenant servers |
| Calendar and Tasks read requests with Bearer tokens |
Calendar/Tasks create, update, or delete operations |
| Expiry buffer, silent re-auth, consent fallback, and reconnect UI |
Provider-specific write logic or product data persistence |
LifeTrkr boundary: This skill covers authentication and read flows. Keep
repo-specific write operations in src/lib/google.ts when that integration
boundary exists; LifeTrkr currently splits the same boundary across
src/lib/googleCalendar.ts and src/lib/googleTasks.ts. Do not move writes into
the auth hook or invent a new backend.
Workflow
1. Plan
Before editing, identify the host origins, existing GIS script, client-ID source,
requested read scopes, auth hook location, and existing Google API modules. Record
the smallest scope set that satisfies the request. Treat the following as the
repo defaults, not universal names:
| Option |
LifeTrkr default |
Porting rule |
| token storage |
browser sessionStorage |
Allow an injected Storage implementation only when required |
| token key |
gal_token |
Configure one key and use it for read, write, validity, and revoke paths |
| expiry key |
gal_expiry |
Configure one key and store an absolute epoch-millisecond expiry |
| expiry skew |
120_000 ms |
Keep a safety buffer; make it configurable for a different latency budget |
| client ID |
VITE_GOOGLE_CLIENT_ID |
Keep it public, environment-backed, and never replace it with a secret |
When porting, pass storage, tokenKey, expiryKey, and expirySkewMs through
one options object. Do not copy hard-coded gal_* names into an unrelated app.
2. Validate
Check the target before implementing:
Confirm index.html loads https://accounts.google.com/gsi/client.
Confirm the client ID is available as a public build-time value and no Client
Secret, .env file, or token is being added to source control.
Confirm GCP has the required APIs enabled and the exact deployed and local
origins under Authorized JavaScript Origins. Leave Authorized redirect
URIs empty for this token model.
Confirm the requested scopes are read-only and minimal. Use
calendar.readonly and/or tasks.readonly only when those reads are needed;
add openid profile email only when identity/profile data is needed.
Run the bundled checker from the project root after the initial setup or a
lifecycle change:
node .agents/skills/okhp3-google-gis-client-auth/scripts/check-gis-setup.cjs
Read assets/gcp-setup-checklist.md for Console setup or
origin_mismatch/invalid_client diagnosis. The checker is advisory: it
cannot prove that OAuth consent, CORS, quotas, or live credentials work.
3. Execute
Implement the smallest change that satisfies the plan:
- Initialize
window.google.accounts.oauth2.initTokenClient with the public
client ID, minimum scopes, prompt: '' for an explicit connect, and a callback
that rejects response.error.
- On success, compute
Date.now() + response.expires_in * 1000, store the token
and absolute expiry using the configured storage/key options, and update UI
state. Default examples for this repository are gal_token and gal_expiry.
- Make
isTokenValid() require a token and
Date.now() < expiry - expirySkewMs; use the same check before every read API
call.
- Make
getToken() return a valid token, otherwise request with
prompt: 'none', then retry with the consent popup only if silent re-auth
fails. Surface a user-actionable reconnect state when both attempts fail or a
read request returns 401.
- Make
disconnect() revoke the current token when GIS is available, remove
both configured session keys, and clear local auth state. Do not persist the
access token in localStorage, a database, a URL, or logs.
- Keep Calendar/Tasks fetch functions read-only in the auth integration. Route
writes through the repository’s existing Google integration boundary.
Use references/useGoogleAuth.ts for a configurable hook baseline. Its
useGoogleAuth(options) contract returns isConnected, accessToken,
minutesUntilExpiry, connect(), getToken(), and disconnect(); adapt the
return shape only when the host application already has a stable contract.
GIS model and API facts
The flow is: popup opens → user consents → GIS returns an access token directly to
the JavaScript callback → the browser calls Google APIs with Authorization: Bearer <token>. There is no redirect callback, server exchange, Client Secret,
or refresh token.
Use the Calendar read endpoint
https://www.googleapis.com/calendar/v3/calendars/primary/events and the Tasks
read endpoints under https://tasks.googleapis.com/tasks/v1/. Check res.ok and
handle 401 as an expired or revoked token; do not silently treat an API error as
an empty result.
If the product needs the signed-in profile, fetch it separately with
https://www.googleapis.com/oauth2/v3/userinfo using the bearer token. Keep that
profile read out of the auth hook unless the host application already treats it as
part of connect(); LifeTrkr keeps it in its Google API integration layer.
The minimum API calls and scopes are:
| Read |
Scope |
| Calendar events |
https://www.googleapis.com/auth/calendar.readonly |
| Task lists/tasks |
https://www.googleapis.com/auth/tasks.readonly |
| Stable profile identity |
openid profile email |
Security and gotchas
- A Client ID is an identifier and is safe to embed; a Client Secret is not. Never
put secrets, access tokens, or
.env files in the bundle or repository.
sessionStorage limits token lifetime to the browser tab. Never silently switch
to localStorage just to survive a reload.
- Use
gal_token and gal_expiry for this repository. For a port, configure the
key names rather than mixing repo defaults with copied examples.
- Store absolute epoch milliseconds, not
expires_in seconds. Apply the same
skew in render-time status and request-time validity checks.
prompt: 'none' can fail because consent was revoked, the browser blocks the
popup/session, or GIS is not loaded. Treat failure as a branch to explicit user
consent, not as permission to bypass auth.
- Authorized JavaScript Origins are origin-only (
scheme://host[:port]); do not
add paths, hash routes, or redirect URIs.
- Request only the scopes used by the feature. Broader scopes increase consent
friction and may trigger verification requirements.
- Audit bundled scripts and references before executing them; a skill is trusted
code and instruction input, not proof that a live OAuth configuration is safe.
Output contract
At handoff, report:
- host origins, client-ID source, requested scopes, and configured storage/key names;
- files changed and whether the change is auth, read, or UI-only;
- validation performed and its result, including checker warnings;
- explicit confirmation that no Client Secret, token, backend, or write operation
was added; and
- any live-environment checks still required (OAuth consent, CORS, quotas, or API
availability).
References and scripts
references/useGoogleAuth.ts — configurable hook baseline; read when implementing
or debugging token lifecycle behavior.
assets/gcp-setup-checklist.md — detailed GCP Console checklist.
scripts/check-gis-setup.cjs — deterministic local setup scan; run from the
target project root and treat warnings as review items.
About
Built by Jamie Hill · OverKill Hill P³
Published at github.com/OKHP3
Part of the OKHP3/skillz Agent Skill library.
MIT License -- free to use, fork, and adapt. A nod to the source is appreciated.
1---2name: okhp3-google-gis-client-auth3description: OverKill Hill P³ client-only Google Identity Services (GIS) auth workflow. Use when designing, implementing, or troubleshooting client-only Google Identity Services (GIS) OAuth token flows for React or TypeScript SPAs on static hosts. Use for Google Sign-In, Calendar or Tasks read access, popup token acquisition, session-scoped token storage, expiry handling, silent re-authentication, GCP OAuth setup, or porting this pattern without a backend, Client Secret, redirect URI, or refresh token. Keep provider-specific write APIs outside this skill.4license: MIT5---67# okhp3-google-gis-client-auth89**OverKill Hill P³** · [overkillhill.com](https://overkillhill.com) · [github.com/OKHP3](https://github.com/OKHP3)1011**Intent:** Give a static-site agent a reliable GIS token lifecycle for Google12API read flows without inventing a backend or leaking a Client Secret.1314## Scope boundary1516| In scope | Out of scope |17|---|---|18| GIS popup token model and GCP Web Application setup | Authorization code flow, refresh tokens, or offline access |19| React/TypeScript `useGoogleAuth` lifecycle | Service accounts, non-Google identity providers, or multi-tenant servers |20| Calendar and Tasks read requests with Bearer tokens | Calendar/Tasks create, update, or delete operations |21| Expiry buffer, silent re-auth, consent fallback, and reconnect UI | Provider-specific write logic or product data persistence |2223**LifeTrkr boundary:** This skill covers authentication and read flows. Keep24repo-specific write operations in `src/lib/google.ts` when that integration25boundary exists; LifeTrkr currently splits the same boundary across26`src/lib/googleCalendar.ts` and `src/lib/googleTasks.ts`. Do not move writes into27the auth hook or invent a new backend.2829## Workflow3031### 1. Plan3233Before editing, identify the host origins, existing GIS script, client-ID source,34requested read scopes, auth hook location, and existing Google API modules. Record35the smallest scope set that satisfies the request. Treat the following as the36repo defaults, not universal names:3738| Option | LifeTrkr default | Porting rule |39|---|---|---|40| token storage | browser `sessionStorage` | Allow an injected `Storage` implementation only when required |41| token key | `gal_token` | Configure one key and use it for read, write, validity, and revoke paths |42| expiry key | `gal_expiry` | Configure one key and store an absolute epoch-millisecond expiry |43| expiry skew | `120_000` ms | Keep a safety buffer; make it configurable for a different latency budget |44| client ID | `VITE_GOOGLE_CLIENT_ID` | Keep it public, environment-backed, and never replace it with a secret |4546When porting, pass `storage`, `tokenKey`, `expiryKey`, and `expirySkewMs` through47one options object. Do not copy hard-coded `gal_*` names into an unrelated app.4849### 2. Validate5051Check the target before implementing:52531. Confirm `index.html` loads `https://accounts.google.com/gsi/client`.542. Confirm the client ID is available as a public build-time value and no Client55 Secret, `.env` file, or token is being added to source control.563. Confirm GCP has the required APIs enabled and the exact deployed and local57 origins under **Authorized JavaScript Origins**. Leave **Authorized redirect58 URIs empty** for this token model.594. Confirm the requested scopes are read-only and minimal. Use60 `calendar.readonly` and/or `tasks.readonly` only when those reads are needed;61 add `openid profile email` only when identity/profile data is needed.625. Run the bundled checker from the project root after the initial setup or a63 lifecycle change:6465 ```bash66 node .agents/skills/okhp3-google-gis-client-auth/scripts/check-gis-setup.cjs67 ```6869 Read `assets/gcp-setup-checklist.md` for Console setup or70 `origin_mismatch`/`invalid_client` diagnosis. The checker is advisory: it71 cannot prove that OAuth consent, CORS, quotas, or live credentials work.7273### 3. Execute7475Implement the smallest change that satisfies the plan:76771. Initialize `window.google.accounts.oauth2.initTokenClient` with the public78 client ID, minimum scopes, `prompt: ''` for an explicit connect, and a callback79 that rejects `response.error`.802. On success, compute `Date.now() + response.expires_in * 1000`, store the token81 and absolute expiry using the configured storage/key options, and update UI82 state. Default examples for this repository are `gal_token` and `gal_expiry`.833. Make `isTokenValid()` require a token and84 `Date.now() < expiry - expirySkewMs`; use the same check before every read API85 call.864. Make `getToken()` return a valid token, otherwise request with87 `prompt: 'none'`, then retry with the consent popup only if silent re-auth88 fails. Surface a user-actionable reconnect state when both attempts fail or a89 read request returns `401`.905. Make `disconnect()` revoke the current token when GIS is available, remove91 both configured session keys, and clear local auth state. Do not persist the92 access token in `localStorage`, a database, a URL, or logs.936. Keep Calendar/Tasks fetch functions read-only in the auth integration. Route94 writes through the repository’s existing Google integration boundary.9596Use `references/useGoogleAuth.ts` for a configurable hook baseline. Its97`useGoogleAuth(options)` contract returns `isConnected`, `accessToken`,98`minutesUntilExpiry`, `connect()`, `getToken()`, and `disconnect()`; adapt the99return shape only when the host application already has a stable contract.100101## GIS model and API facts102103The flow is: popup opens → user consents → GIS returns an access token directly to104the JavaScript callback → the browser calls Google APIs with `Authorization:105Bearer <token>`. There is no redirect callback, server exchange, Client Secret,106or refresh token.107108Use the Calendar read endpoint109`https://www.googleapis.com/calendar/v3/calendars/primary/events` and the Tasks110read endpoints under `https://tasks.googleapis.com/tasks/v1/`. Check `res.ok` and111handle `401` as an expired or revoked token; do not silently treat an API error as112an empty result.113114If the product needs the signed-in profile, fetch it separately with115`https://www.googleapis.com/oauth2/v3/userinfo` using the bearer token. Keep that116profile read out of the auth hook unless the host application already treats it as117part of `connect()`; LifeTrkr keeps it in its Google API integration layer.118119The minimum API calls and scopes are:120121| Read | Scope |122|---|---|123| Calendar events | `https://www.googleapis.com/auth/calendar.readonly` |124| Task lists/tasks | `https://www.googleapis.com/auth/tasks.readonly` |125| Stable profile identity | `openid profile email` |126127## Security and gotchas128129- A Client ID is an identifier and is safe to embed; a Client Secret is not. Never130 put secrets, access tokens, or `.env` files in the bundle or repository.131- `sessionStorage` limits token lifetime to the browser tab. Never silently switch132 to `localStorage` just to survive a reload.133- Use `gal_token` and `gal_expiry` for this repository. For a port, configure the134 key names rather than mixing repo defaults with copied examples.135- Store absolute epoch milliseconds, not `expires_in` seconds. Apply the same136 skew in render-time status and request-time validity checks.137- `prompt: 'none'` can fail because consent was revoked, the browser blocks the138 popup/session, or GIS is not loaded. Treat failure as a branch to explicit user139 consent, not as permission to bypass auth.140- Authorized JavaScript Origins are origin-only (`scheme://host[:port]`); do not141 add paths, hash routes, or redirect URIs.142- Request only the scopes used by the feature. Broader scopes increase consent143 friction and may trigger verification requirements.144- Audit bundled scripts and references before executing them; a skill is trusted145 code and instruction input, not proof that a live OAuth configuration is safe.146147## Output contract148149At handoff, report:150151- host origins, client-ID source, requested scopes, and configured storage/key names;152- files changed and whether the change is auth, read, or UI-only;153- validation performed and its result, including checker warnings;154- explicit confirmation that no Client Secret, token, backend, or write operation155 was added; and156- any live-environment checks still required (OAuth consent, CORS, quotas, or API157 availability).158159## References and scripts160161- `references/useGoogleAuth.ts` — configurable hook baseline; read when implementing162 or debugging token lifecycle behavior.163- `assets/gcp-setup-checklist.md` — detailed GCP Console checklist.164- `scripts/check-gis-setup.cjs` — deterministic local setup scan; run from the165 target project root and treat warnings as review items.166167## About168169Built by [Jamie Hill](https://overkillhill.com) · [OverKill Hill P³](https://overkillhill.com)170Published at [github.com/OKHP3](https://github.com/OKHP3)171Part of the [OKHP3/skillz](https://github.com/OKHP3/skillz) Agent Skill library.172MIT License -- free to use, fork, and adapt. A nod to the source is appreciated.