-
Notifications
You must be signed in to change notification settings - Fork 0
auth and session
How the SPA authenticates the user and how it stays signed in.
The model is Auth0 for end-user identity → gateway for
session. The SPA never holds a long-lived API token. The
gateway issues a short-lived session_token that travels on
every request.
In-repo references:
src/auth/src/api/gatewaySession.ts- In-repo design:
docs/auth/auth0-setup.md - In-repo design:
docs/auth/gateway-session-pattern.md
┌──────────┐ 1. Auth0 SPA login ┌──────────┐
│ Browser │ ◄────────────────────────────► │ Auth0 │
│ │ (id_token, hash flow) │ │
└────┬─────┘ └──────────┘
│
│ 2. POST /api/auth/login { id_token }
▼
┌──────────────────────────┐
│ NoETL Gateway │
│ - verifies id_token │
│ against Auth0 JWKS │
│ - upserts user │
│ - issues session_token │
└──────────┬───────────────┘
│ 3. response: { session_token, expires_at, user_id }
▼
┌──────────┐
│ Browser │ stores session_token in localStorage
│ │ includes it on every subsequent gateway request
└──────────┘
Subsequent requests carry the session_token. On app load, the
SPA validates the token (POST /api/auth/validate) before
trusting it; on validation failure, the SPA clears storage and
routes to Auth0 login.
| Concept | Where | Why |
|---|---|---|
| Auth0 tenant config |
VITE_AUTH0_DOMAIN, VITE_AUTH0_CLIENT_ID (build-time) |
Identifies the Auth0 application the SPA logs into. |
| Auth0 ID token | Browser memory (Auth0 SDK in-memory only) | Short-lived. Exchanged for gateway session immediately. |
| Gateway session_token | Browser localStorage
|
Used for all subsequent gateway calls until expiry. |
| Auth0 client secret | Gateway environment (k8s Secret) | Never reaches the browser; only the gateway needs it to verify ID tokens. |
| User record | Gateway DB (auth.users) |
Upserted on first login. |
| Session record | Gateway DB (auth.sessions) |
Tracks active sessions, expiry, IP. |
The SPA bundle has zero secret material. The Auth0 domain and SPA client ID are public values (they appear in Auth0's authorize URL).
The user-facing flow happens partly in NoETL. The gateway's
/api/auth/login does not write to the DB itself — it dispatches
the api_integration/auth0/auth0_login playbook (or its
optimized variant) which:
- Validates the JWT signature against Auth0 JWKS.
- Upserts the user row in
auth.userskeyed byauth0_id. - Inserts a new row in
auth.sessionswith a randomsession_tokenand 24-hour TTL. - Caches the session in NATS KV (bucket
sessions). - Sends
send_success_callbackback to the gateway, which forwards the result to the SPA over SSE.
Source (the optimized 3-step variant):
fixtures/playbooks/api_integration/auth0/auth0_login_optimized.yaml.
Even the auth flow itself is a playbook. The gateway calls one; it doesn't open a DB connection itself.
For local development, the SPA can run without authentication:
VITE_ALLOW_GUEST=true
In guest mode, the SPA calls NoETL directly (/execute) instead
of going through the gateway's authenticated GraphQL endpoint.
Use this for local SPA iteration against a local NoETL.
Guest mode is never used in production. The gateway URL is set
to a Cloudflare-fronted address that requires the
session_token.
The "Auth0" part of the flow is replaceable. To swap in a different OIDC provider (e.g. Cognito, Keycloak, Google Identity Platform):
- Change
@auth0/auth0-reactto your provider's SDK in the SPA. The contract you need at the SPA layer is just "give me an OIDC ID token". - Update the gateway's auth0 verification logic to point at the new JWKS. The gateway has the JWKS URL in its config.
- Adjust the
auth0_loginplaybook (or write a new one) to handle any provider-specific token claims you care about (thenickname,email,picturefields, etc). - Update the relevant
VITE_*env vars at build time.
The rest of the stack — SSE delivery, session token, playbook dispatch — does not care which OIDC provider issued the ID token. The gateway's session model is intentionally provider-agnostic.
Gateway sessions expire after 24 hours by default
(INTERVAL '24 hours' in the auth0_login playbook's
create_user_session step). On expiry:
- The SPA's next gateway call returns 401.
- The SPA clears storage and redirects to Auth0 login.
- The Auth0 SDK silently re-authenticates if the Auth0 session is still live (otherwise prompts for credentials).
There is no refresh-token flow today. If the SPA needs longer sessions, increase the playbook's interval.
For pushback against "could we just put X in env":
- Auth0 client secret? Gateway only. Never in SPA env, never in worker env.
- Database connections used by the auth playbook? Pulled from
the keychain by alias (
{{ db_credential }}) — currentlypg_auth. The playbook references the alias; the keychain resolves to the actual DSN at step execution. - NATS connection used by the auth playbook? Same pattern,
{{ nats_credential }}.
See the secrets-and-credentials rule in the architecture doc.
- Gateway integration — the wire protocol the SPA uses with the gateway.
- Architecture — overall layering.
- In-repo:
docs/auth/— implementation-level details.
Travel SPA
Architecture
- Architecture
- Widget contract
- Business data via playbooks
- Playbook: itinerary-planner
- Playbook: calendar/list
Integration
Operations
See also
- noetl wiki (app)
- ops wiki (deploy)
- Ephemeral Blueprints