Architecture Decision Record

0037. Consent and the authorization-grant model

Context

The OAuth surface (ADR-0014) issues scoped tokens, but consent — what a user agreed a client may access — and scope-grant enforcement were undecided (§12.8). Consent is a CIAM and privacy requirement (GDPR consent receipts) and a scope-escalation defense; without a model, a client could obtain scopes the user never approved.

Decision

  1. Consent is recorded per (principal, client, scope set) as a versioned directory record (ADR-0011) — a durable, auditable consent receipt (who consented to what, when) and an event (ADR-0022).

  2. Incremental, granular consent. A client requests scopes; the user consents per-scope; previously-consented scopes are not re-prompted, newly-requested ones are. A token carries only granted scopes (ADR-0014), and claims are gated by granted scope (proposal 0008) — least disclosure.

  3. Scope-escalation defense. A client cannot obtain a scope the user has not consented to. First-party / trusted clients may skip the consent screen by realm policy (ADR-0012) — the grant is then recorded as an implicit grant (policy-made, revocable, and audited exactly like a prompted one), so "what may this client access" has one answer for both kinds; third-party clients cannot skip the prompt.

  4. Admin consent — the org-level grant. Workforce adoption needs the Entra-shaped counterpart of user consent: a realm admin (delegated administration, proposal 0016) grants a client a scope set for the whole realm/org, recorded as a single org-level grant object — versioned, evented (ADR-0022), and withdrawable exactly like a user grant — that suppresses the user prompt for the covered scopes. Two controls come with it: an app-consent policy per realm declares which scopes users may self-consent and which require admin consent (directory-wide reads never ride a single user's click); and an admin-consent request workflow — a user hitting an admin-required scope files a request an admin approves or denies, a workflow over the event stream in the ADR-0030 shape. Admin-consent withdrawal revokes dependent tokens realm-wide through the same path as decision 5.

  5. Withdrawal. Consent withdrawal (self-service, proposal 0010; admin surface for org-level grants) revokes the grant and the dependent tokens (ADR-0005), so "stop sharing with this app" actually stops it within the freshness budget. Because consent records are otherwise eventually-consistent directory data (ADR-0011), withdrawal alone would race future issuance: a lagging replica could mint a token carrying the withdrawn scope after the dependent-token revocation already ran, and that token would never be covered. The withdrawal write therefore returns a consistency token, and issuance evaluates consent as a fenced read (ADR-0004) — Modgud never mints from a consent state older than the last withdrawal it has been shown. Withdrawal is the one consent operation on the strong path; ordinary consent reads stay edge-local.

Consequences

  • Consent is a first-class, auditable, withdrawable record — the GDPR consent-receipt requirement and the user-control expectation, both met.
  • Scopes cannot escalate past consent; the first-party-vs-third-party consent experience is realm policy, not hard-coded.
  • Consent records compose with crypto-shredding (ADR-0034) and the privacy posture (ADR-0027) — they are PII-adjacent and erasable.
  • Refines ADR-0014 and composes with proposals 0008 / 0010; closes the §12.8 scope/consent gap.