# ADR 0016. BFF Cookie Sessions and Machine OAuth

## Status
Accepted.

Supersedes [ADR 0003](0003-session-jwt-at-boundary.md) (signed JWT at operator
login for an embedded widget). Gameplay independence is [ADR 0015](0015-async-loyalty-sidecar.md).

## Context
A partner-readable session JWT on every game action would put PlayerPal
claims in the hot path, encourage putting PII or live balance in tokens, and
depend on third-party cookies for widgets. Browser `localStorage` refresh
tokens are a theft target.

## Decision
**Player web:** Authorization Code + PKCE through a backend-for-frontend.
Session is an `HttpOnly`, `Secure`, `SameSite` cookie. The browser token, if
any, carries the IAM subject, not the private player UUID and not live
loyalty state. Mapping `iss + sub` → player is server-side.

**Embedded operator/provider UX:** first-party PlayerPal popup, opaque
one-time codes, exact `postMessage` origin checks. No assertion or access
token in URLs. No third-party cookies. No refresh tokens in the widget.

**Machine clients:** OAuth 2.0 client credentials with `private_key_jwt`.
Access-token lifetime 5-15 minutes. Cached JWKS for validation. Key overlap
during rotation.

## Consequences
- Claim-set and token-lifetime open questions from ADR 0003 are closed for
  the profiles above (spec §13).
- Embedded widgets cannot silently keep a PlayerPal session after popup close.
- See [docs/authentication.md](../authentication.md) and spec §14.

## Amendment
Local POC also establishes the BFF cookie via Nest Session/User API password
and passkey flows and Nest-owned social IdP success
([ADR 0024](0024-nest-owned-portal-auth.md)), not only Auth Code + PKCE through
Login V2. Cookies must be first-party on the portal host
(`player.localhost` path-routed Nest).
