# ADR 0024. Nest-Owned Portal Auth (Session API + Social IdP Intents)

## Status
Accepted.

## Context
ADR 0022 chose a Login V2 fork for branded portal sign-in. In the local POC,
password and passkey on Login V2 conflicted with player rules (no email forms,
PlayerPal ID as username, RP id on `player.localhost`) and with Session API
limits (CreateSession cannot challenge without a user id — zitadel#8899).
Social still needs ZITADEL IdP intents; password/passkey need to stay on the
portal card. Same-origin Nest under `player.localhost` is required so the
BFF session cookie is first-party after Google return (ADR 0016).

## Decision
1. **Password and passkey** for the player portal are owned by Nest against
   ZITADEL User + Session APIs (`POST /auth/password-*`, `/auth/passkey/*`,
   `/auth/factors/*`). Login V2 is **not** the password/passkey UX.
2. **Social** (Google / Apple / Facebook) starts from Nest (`GET /auth/login`
   or IdP intent → `/auth/idp/success`). Creates/links an IAM human whose
   **username is `pp_plr_…`**, throwaway password the player never sees, then
   issues the PlayerPal BFF cookie. No Login V2 email form (ADR 0012).
3. Store a **public-key mirror** in `webauthn_credentials` for discoverable
   (usernameless) passkey login on the portal RP. Never store private keys
   or player PII. Identifier-first passkey may still use Session API with
   `userId`.
4. Persist opaque IAM subject as `authentication_identities.iam_subject` for
   password sign-in (local lookup first; ListUsers by username only when
   missing). Keep `player_sessions.iam_user_id` for signed-in factor add.
   Neither is player PII; neither appears on `/v1/me`.
5. **Logout** clears the PlayerPal cookie only (no ZITADEL `end_session` hop
   that parks on Login V2).
6. Login V2 fork (ADR 0022) remains available for experiments / future skins;
   it is **not** the authoritative player password/passkey path for this POC.

## Consequences
- Supersedes ADR 0021 item 6 and ADR 0022 items 2 and 5 for the local player
  portal auth path (see amendment notes on those ADRs).
- WebAuthn RP is the portal host (`WEBAUTHN_RP_DOMAIN` / `APP_BASE_URL`, e.g.
  `player.localhost`), not `localhost` Login V2.
- Day-to-day docs: [docs/authentication.md](../authentication.md),
  [infra/README.md](../../infra/README.md).
- Does not close production IAM vendor choice (§23.2).
