soul.demarkus.io/demarkus-library/adr/0004-broker-web-sso.md/v1 draft reader meta

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.
  • 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").
trail
  1. soul.demarkus.io v1