Enhancement Proposal

Proposal: Token vault — upstream-IdP token storage and brokerage

Motivation

When a principal signs in through an upstream provider (ADR-0019), the provider hands back more than identity: an access token (and often a refresh token) for its APIs — the Google token that can read a calendar, the GitHub token that can list repos. Today's design discards the question; every application that wants to call the upstream API on the user's behalf must collect, store, refresh, and secure those tokens itself — N applications × M providers of scattered, long-lived credentials. Auth0 ships this as Token Vault; agentic integrations have made it table stakes. Vord should hold these tokens once, centrally, under the protections identity data already gets.

Design

  1. The vault stores upstream tokens per (principal, connection), issuance-side. Vault entries are written when a federated login or an explicit connection-link ceremony (ADR-0019) yields upstream tokens, keyed by the principal's immutable id and the upstream connection. The vault is Modgud-side state: never edge-visible, never on the verification path — rule 1 is untouched because a vault read is an explicit issuance-side operation, not token verification.

  2. Vault content is PII-grade: per-principal encryption, erasure included. Entries are encrypted under the principal's data key (ADR-0034), so crypto-shredding erases stored upstream tokens with everything else, and a vault row is unreadable without the principal's key. Upstream tokens are the most abusable material Vord would hold besides signing keys; they get the strongest at-rest posture the platform has.

  3. Access is a token exchange, never an embedded claim. A registered, consent-gated client obtains an upstream token by presenting its Vord access token to an exchange endpoint (the RFC 8693 seat, ADR-0014) naming the connection and requested upstream scopes. Upstream tokens are never embedded in Vord-issued JWTs — they would blow the size budget (specs/vord-token-v0 §4) and turn every bearer leak into an upstream compromise. The exchange is authorized by client registration (ADR-0038) plus a consented vault scope (ADR-0037) — withdrawal cuts vault access exactly as it cuts any scope.

  4. The vault owns refresh. Stored refresh tokens are used server-side to keep access tokens live; an upstream refresh failure (revoked at the provider, expired grant) marks the connection relink-required and emits an event (ADR-0022) — the application sees a clean "re-link your account" signal instead of scattered 401s.

  5. Every vault access is audited. Reads, refreshes, exchange grants, and relink events are stream events; the per-principal vault view (which apps accessed which connection, when) surfaces in self-service (proposal 0010). The vault is also a named high-value asset in the threat model — its addition is exactly the "new component" trigger of ADR-0027 §4.

Alternatives considered

  • Each application stores its own upstream tokens. The status quo being solved: multiplies long-lived credential copies, defeats central revocation and erasure, and makes consent withdrawal unenforceable.
  • Embed upstream tokens in the Vord session or JWT. Rejected: size, and it converts every Vord bearer into an upstream bearer — the blast-radius opposite of the edge model's design.
  • A generic secrets manager. Rejected: lifecycle is identity-shaped — bound to principal + connection + consent + erasure — none of which a KV-secrets engine models; proposal 0004 governs operational secrets, this governs user-delegated ones.

Graduation criteria

  • A federated principal's upstream tokens are captured at link time, encrypted per-principal, and retrievable by an authorized client via the exchange — with consent enforced and withdrawal cutting access (abuse-case tested).
  • Server-side refresh keeps a stored token live across the upstream expiry; a revoked upstream grant produces the relink-required event, not silent failure.
  • Erasure (ADR-0034) verifiably renders vault entries unreadable.
  • Vault access appears in the audit projection and the principal's self-service view.

Backward compatibility

  • Additive: a new issuance-side store and one exchange grant profile on the reserved RFC 8693 seat. No token-format change; no new claim.
  • The connection-link ceremony (ADR-0019) gains an optional "capture tokens into the vault" outcome, off by default per realm until enabled.

Open questions

  • Hand-out vs proxy mode: return the upstream token to the client (this draft), or keep it server-side and proxy the downstream call? Proxying shrinks exposure but puts Vord on the data path — likely a per-connection policy rather than one answer.
  • Scope narrowing on exchange: request a subset of stored upstream scopes, or always hand out what the vault holds?
  • Whether vault-holding realms need a shorter erasure SLA than the general ADR-0034 posture, given the material's sensitivity.