# ADR 0004 — Broker confidential web client + redirect SSO (reject device flow) **Status:** Accepted (2026-06-11) · **Phase:** 1b ## Context The library is a **server-deployed** web app. The broker (`/oauth/authorize`) enforces **loopback-only** redirect URIs (`oauth_authorize.go:54-60`, RFC 8252 §7.3) because it has only ever served native/CLI agents (Claude Code's MCP SDK catches the redirect on a throwaway loopback listener). A deployed web app has no loopback, so the auth-code redirect flow the plan assumed is a dead end at a real HTTPS domain. `/mcp` is always bearer-gated (`mcp_auth.go:33-65`) — login is a hard turnstile, no anonymous reads. Three options were weighed: - **A. Device flow (RFC 8628).** Works with no redirect, no broker changes. But it's built for input-constrained devices; on a browser it's a tab hop + code entry + polling — permanent jank. A shortcut. - **B. Broker confidential web client + redirect SSO.** Add a registered web-client class to the broker; library does standard one-redirect web SSO. Broker work, security-sensitive, but the normal shape of an OAuth server. - **C. Fuse broker into the library.** Rejected on sight — see below. ## Decision 1. **Broker and library remain separate components, cohesive through the protocol.** The broker is the org's single auth + MCP gateway boundary serving *all* clients (Claude Code, CLI, Obsidian plugin, library). Fusing it into the library would make shared infrastructure app-specific, couple release cadences, and duplicate the auth boundary at the second web app. Every client — including Claude Code — reaches a world the same way: OAuth to the broker, MCP through `/mcp`. That sameness *is* the cohesion. 2. **Adopt option B.** Teach the broker a confidential web-client class (registered https redirect, `authorization_code` grant with client authentication) **alongside** the untouched native loopback path. The library does standard redirect-based web SSO. The device flow (A) is rejected as a shortcut. 3. **Tokens live server-side only.** The browser holds an opaque session cookie; id/refresh tokens never reach it (XSS boundary). ## Consequences - A mature auth gateway serves both client classes: native/CLI (loopback auth-code + device) and web (registered confidential client). Closing the web gap is the right long-term investment, and it unblocks the plan's Phase 5 "public face" without rework. - Phase 1b spans two repos; the broker change is a prerequisite for library redirect SSO. Sequenced in [the Phase 1b plan](/demarkus-library/plans/phase-1b-web-sso.md). - New broker security surface — exact-match redirect validation, confidential client auth — must be done carefully (constant-time secret compare, hashed at rest, https-only, loopback path isolated so native agents are unaffected). - The library's `WorldGateway`/`ReadingService` ports gain a `context.Context` arg to thread the per-request session bearer (small, principled deviation from "core untouched").