# ADR 0022. Official ZITADEL Login App Fork for Portal Sign-In

## Status
Accepted.

## Context
Portal sign-in must feel like PlayerPal, not stock ZITADEL. Console branding
cannot change layout or hide email-centric paths enough for player rules
(ADR 0012, ADR 0020, ADR 0021). Building a login UI from scratch against the
Session API duplicates what ZITADEL already ships as MIT `apps/login`. The
community `zitadel-login-ui` is useful as visual inspiration only: it is not
an official product, documents `IAM_OWNER`, and is unverified against Login V2
finalize / OIDC proxy requirements.

All portals (player, staff, operator, provider, affiliate) will eventually
need a custom login look. Starting with five skins or five deploys is premature
while only the player portal has real Nest + ZITADEL auth.

Password-on-first-signup conflicts with a naive reading of ADR 0020: a
`pp_plr_...` does not exist before registration, and player-chosen usernames
are out. Cold create still needs a non-email loginname in IAM.

## Decision
1. Customize portal login by **forking the official ZITADEL Login V2 app**
   (`apps/login`), not by console-only branding, not by build-your-own Session
   UI, and not by adopting community `zitadel-login-ui` as the product.
2. Keep Nest as BFF (Auth Code + PKCE, `openid` only, `iss+sub` → player).
   The Login app stays on the **same public origin** as the ZITADEL API
   (POC: Traefik `/ui/v2/login` on `localhost:8080`). PlayerPal does not store
   WebAuthn credentials.
3. **v1 scope:** one fork, one deploy, **player-portal look**. Per-portal
   skins and method sets come later; do not split merge trains yet.
4. Community login UI may inform layout and polish only. Service principal
   remains `IAM_LOGIN_CLIENT`, not `IAM_OWNER`.
5. Player create on the fork must support **passkey**, **Google**, and
   **provisional PlayerPal ID + password on the first signup screen**. For
   password create: PlayerPal **pre-mints** `pp_plr_...`, IAM uses that value
   as the loginname (never email, never a player-chosen username), Nest binds
   `iss+sub` to that row, assurance starts at **A0** (ADR 0020) until a
   stronger proof is added. No email verification on the player path;
   `EMAIL_VERIFICATION` stays off for players.
6. ZITADEL + this fork remain **contingent** on ADR 0021 / §23.2. Kill the
   fork path if the IAM checklist fails, player flows cannot stay email-free,
   or upstream Login merges cost more than we will budget (default signal:
   about five engineer-days per quarter). Local passkeys are disposable;
   durable enrollments only on the permanent auth host.

## Consequences
- Infra will replace or overlay `ghcr.io/zitadel/zitadel-login` with the
  forked image; upstream sync of login + `@zitadel/client` / proto is an
  ongoing cost (see ZITADEL fork-and-deploy docs).
- `docs/authentication.md` and the local runbook should describe the forked
  Login entry, player-portal chrome, and pre-minted `pp_plr_...` password
  create.
- Later per-portal looks should prefer one fork / one merge train with
  client- or path-selected themes, not independent forks.
- Open question “whether PlayerPal ID + password is offered” is **yes** for
  player portal signup, under ADR 0020 provisional rules plus pre-mint here.
- Does not by itself close production IAM vendor choice (§23.2).

## Amendment (ADR 0024)
For the local player portal, password create/sign-in and passkey ceremonies
are Nest Session/User API paths, not “on the Login V2 first screen.” Nest may
store a WebAuthn **public-key** mirror for discoverable login. The Login V2
fork remains optional for skins/experiments; authoritative player
password/passkey UX is [ADR 0024](0024-nest-owned-portal-auth.md).
