[C4] OAuth broker walking skeleton — indigo ClientApp vs live PDS
closedGoal
Prove the signup/login authn path end-to-end: a Go internal/authbroker using indigo’s atproto/auth/oauth ClientApp that performs a complete confidential-client atproto OAuth flow against the live local PDS container from C1 (and once against a community PDS for federation sanity), binding the resulting session to a sovrn identity row.
Context: docs/02-identity-and-auth.md §1–§3, docs/07-language-and-stack.md §2.
Tasks
- [ ] Vendor/pin indigo; minimal Go module wiring per C4 of language doc
- [ ] Publish client-metadata document; generate ES256 attestation key into file-based secret store (dev); register as confidential client (
private_key_jwt,dpop_bound_access_tokens) - [ ] Postgres-backed
ClientAuthStore(auth requests + sessions, multi-session per account) - [ ]
/oauth/login+/oauth/callbackhandlers: PAR, PKCE, DPoP nonces, refresh handling - [ ] Enforce indigo’s mandatory checks incl.
sub == starting DIDandissconsistency; reject otherwise - [ ] Identity-only scope profile first (
atproto); second test with granularrepo:com.sovrn.mail.settings…scope to validate partial-grant behaviour - [ ] Integration tests in CI against C1 PDS container; one manual test vs external PDS recorded in issue
Acceptance criteria
- CI test: full browser-simulated flow (HTTP client following redirects) yields session bound to expected DID
- Tamper tests: mismatched
sub, wrongiss, expired code → rejected with clear errors - Session survives process restart via DB store; refresh rotates tokens correctly
Risks this should surface
- indigo API instability / gaps vs current alpha spec (scope string handling is “simple strings” upstream)
- Confidential-client attestation requirements differing across AS implementations (entryway vs plain PDS)
- DPoP nonce edge cases on token endpoint
2 Comments
SQLite ClientAuthStore + Broker landed — 2026-08-26
Built the C4 authbroker skeleton against indigo, with the SQLite-backed auth store (per the RTW reference evaluation).
What was built
internal/authbroker/store.go— SQLite-backed indigooauth.ClientAuthStore(6 methods), adapted from../rtw/default/server/atproto/store.go. Schema:oauth_session(did, session_id, data BLOB, mtime)unique on (did,session_id);oauth_request(state, data BLOB, mtime)unique on state; 6-month GC triggers. Driver-agnostic (database/sql);SaveSessionupsert +SaveAuthRequestInfocreate-only. Unit tests ported from RTW (stdlib, no testify).internal/authbroker/authbroker.go—Brokerwrapping indigooauth.ClientAppas a confidential client (private_key_jwt, ES256 attestation key).StartLogin/CompleteLogin/ResumeSession/Logout, plusClientMetadata()(embedding the attestation JWKS inline — indigo leaves confidential JWKS to jwks_uri, so we set it) andJWKS().v0.0.0-20260815054354-160dac5de9e8(same as RTW).Decision reversal (record for docs/ADR-0002)
SQLite driver is
mattn/go-sqlite3(cgo), NOTmodernc.org/sqlite. Owner chose cgo+mattn. Implication:CGO_ENABLED=1and a C toolchain are now required for the build/CI (gcc is present in the devenv shell). ADR-0002 “pure-Go, no cgo” rationale is superseded; docs/01 §2 and docs/07 §4 (modernc.org/sqlite) need updating when convenient.RTW evaluation (the ask)
../rtw/default/server/atproto/store.gois a clean, completeoauth.ClientAuthStore— persistence-only, driver-agnostic, correct upsert/create-only semantics. Directly reusable; only the driver (mattn already, actually) and test framework (testify→stdlib) differed. The flow itself lives in indigoClientApp, which is the right split.Remaining for C4 (the live-PDS E2E)
Not yet done; this is the substantial part:
did:plc:*DIDs that are NOT on the public plc.directory. indigoidentity.Directorymust be pointed at the local PDS (com.atproto.identity.resolveHandle+com.atproto.repo.describeRepoboth work). Needs a small customidentitywrapper (component B8).host.containers.internalpattern proven in the C5 OIDC test.Account creation verified live:
com.atproto.server.createAccount→alice.test/did:plc:umr4euvtxxs722d5t4taw6me, PDS auth-server metadata has PAR/DPoP/private_key_jwt/ES256 all supported.C4 E2E deferred to C6 — 2026-08-26
Decision: the live-PDS OAuth E2E is deferred to C6 (0d541aa), which already plans the browser-sim atproto login as part of its happy path. C4 is complete at store + broker.
Rationale tied to the did:plc clarification (docs/06 §1): production hosts a public-directory did:plc PDS, so the production identity path uses standard indigo directory resolution — no production local-resolver code is warranted. The local dev PDS (non-public did:plc) is a dev-only artifact, so the local-resolution wrapper would be throwaway dev plumbing better folded into C6’s login step where the whole flow is exercised anyway.
Store + Broker (SQLite/mattn, confidential-client) remain the C4 deliverable and are unit-tested green.