ADR 0021. NestJS Control Plane + ZITADEL Local POC (Google IdP)
Status
Accepted.
Context
PlayerPal needs a real local vertical: IAM, Google social login, Postgres identity graph, BFF sessions, and link-intent. The team's preferred backend split is:
- NestJS for main business logic, CRUD, and the interactive control plane
- Go for small, parallel, latency-sensitive data-plane workers later
ZITADEL publishes a NestJS PKCE example (https://zitadel.com/docs/sdk-examples/nestjs). Spec §35 suggested Go for the core API; that remains appropriate for ingestion workers, but the control plane aligns better with NestJS next to the React portals.
Open question #1 (Keycloak vs ZITADEL) needs a POC vehicle for local/dev.
Decision
- Use ZITADEL as the local/dev IAM POC (Google configured as a ZITADEL IdP).
- Implement the PlayerPal control plane as NestJS (
services/api): Auth Code + PKCE, HttpOnly session cookies,iss+sub→pp_plr_..., link-intent stubs. Follow ZITADEL's NestJS/PKCE pattern; requestopenidonly. - Keep PostgreSQL (
playerpal-db) as the PlayerPal system of record for players, aliases, and links, separate from ZITADEL's DB. DDL ownership is TypeORM migrations in Nest (ADR 0023), not hand-maintainedschema.sql. - Reserve Go (
services/worker) for future ingestion/loyalty workers. - Never persist player email, name, or phone (ADR 0012). Strip prohibited claims before any PlayerPal write.
- Treat passkey, Google, and provisional PlayerPal ID as first-class portal
entry points into hosted login (
prompt=login/prompt=create). PlayerPal does not implement WebAuthn credential storage; RP-initiated logout clears the IAM session after the PlayerPal cookie.
Keycloak remains a valid alternative if the ZITADEL POC fails spec §23.2.
Amendment (ADR 0023 / 0024)
Item 6 is superseded for the local player portal: password/passkey and social create are Nest-owned; a public-key mirror may be stored for discoverable passkeys; logout clears the PlayerPal cookie only. See ADR 0024. DDL path is TypeORM (ADR 0023).
Consequences
- Local runbook:
infra/README.md(login policy, Google IdP id, passkeys). - Production IAM pick is still gated on the §23.2 checklist, not this ADR alone.
- Data-plane scale path stays open for Go without rewriting NestJS identity CRUD.
- Google OAuth redirect URI is ZITADEL
/idps/callback, not the NestJS app. - Silent SSO off a leftover console session is not acceptable for the player portal.