0038. The OAuth client model — registration, authentication, and redirect-URI validation
Context
ADR-0014 fixed the flow set; ADR-0021 made client registration part of the management API; the threat catalog (§12.5) already credits "exact redirect-URI matching" to ADR-0014 and "client secrets" appear in secret handling (proposal 0004). But no record actually defines the client (the relying party as Vord stores it) as a first-class security object: its types, how it authenticates, how its secret rotates, and the redirect-URI validation policy that is the linchpin against open-redirect and authorization-code interception. That validation is the single most load-bearing control on the front channel — the OAuth Security BCP (RFC 9700) names loose redirect matching as a root cause of code-injection and mix-up — yet it has no owning decision. This ADR gives the client its model before the OAuth surface hardens.
Decision
-
The client is a first-class, realm-scoped registered object (ADR-0012), created through the management API (ADR-0021) and identified by a
client_id. Two types: public (no secret; PKCE is the only proof, mandatory per ADR-0014) and confidential (authenticates to the token endpoint). The type is fixed at registration and constrains everything below. -
Confidential-client authentication is pluggable, with a rotation discipline that mirrors signing keys. v1: a
client_secretstored verify-only (hashed, never recoverable — proposal 0004), presented asclient_secret_basic(primary) orclient_secret_post(compat) per ADR-0014's discovery metadata. Reserved structural seats:private_key_jwtand mTLS client authentication (the FAPI methods, ADR-0025). A client may hold multiple concurrent secrets, each keyed for rotation exactly as signing keys arekid-addressed (ADR-0023): register the new secret, overlap, retire the old — no flow interruption, no flag-day. Secret rotation is a management-API operation and an event (ADR-0022). -
Redirect-URI validation is exact-match against a registered allowlist. No wildcards, no substring or prefix matching, no port/path flexibility beyond what is registered — the control RFC 9700 makes non-negotiable. The only relaxations are the explicit, bounded exceptions the standards carve out (loopback redirects for native apps with variable ports); each is named, not a general loosening. This refines the ADR-0014 attribution: redirect-URI matching now has an owning decision.
-
Per-client configuration is realm-scoped policy. A client registers its allowed grant types (a subset of ADR-0014's set), its allowed scopes (gated by consent, ADR-0037), its token TTLs and its refresh-token family lifetimes (idle and absolute, ADR-0044) — all within the realm's budget profile (ADR-0039) — and its allowed authentication methods. A client cannot request a grant, scope, or redirect target it is not registered for — the request is refused, not silently widened.
-
Dynamic Client Registration (RFC 7591) is the same model as a self-serve endpoint — reserved, not v1. DCR is the registration of decision 1 exposed for programmatic onboarding, gated by an initial-access token and rate-limited (proposal 0006). The static management-API registration is v1 (M0 registers one client so the auth-code flow can run); DCR is additive on top of the same object, the way ADR-0014's other reserved features are additive.
-
Disabling or deleting a client revokes its live tokens. Deprovisioning a client joins the same status-list + NATS kill path as deprovisioning a principal (ADR-0005), so a compromised or retired client's outstanding tokens die within the revocation freshness budget rather than living to their TTL.
Consequences
- The most dangerous front-channel control — redirect-URI matching — gains an explicit owner and an exact-match rule, closing the dangling §12.5 attribution to ADR-0014.
- Client-secret rotation reuses the signing-key rotation discipline (publish/overlap/ retire, ADR-0023): one rotation pattern for two kinds of secret, so a leaked client secret is rotated without downtime instead of being a flag-day.
- DCR stays a reserved seat over the same object, so self-serve onboarding is a later composition change, not a re-model — consistent with ADR-0014's reserved-seat habit.
- Clients are resources in the Forseti graph (ADR-0021), so "who may manage this client" is ordinary authorization, not a second admin path.
- Despite its late number this is effectively an M0 ADR: the walking skeleton registers its one client through this model — client type, hashed secret, and exact-match redirect validation are skeleton-day facts, since retrofitting irreversible secret hashing or tightening redirect matching after clients exist is a breaking change.
- Refines ADR-0014 (redirect-URI validation and per-client policy) and ADR-0021 (the registration surface it administers).