Skip to content

docs(auth): scope=commerce selects the commerce consent; the authorization response carries RFC 9207 iss - #404

Draft
r-marques wants to merge 1 commit into
mainfrom
docs/3527-oauth-scope-consent-and-iss
Draft

r-marques wants to merge 1 commit into
mainfrom
docs/3527-oauth-scope-consent-and-iss

Conversation

@r-marques

Copy link
Copy Markdown
Member

Companion to nevermined-io/nvm-monorepo#3532 (implements nevermined-io/nvm-monorepo#3527, part of epic nevermined-io/nvm-monorepo#3458 — ADR 0077). Documents two AS-side behaviours that PR ships; merge after it deploys so the docs never lead the API.

What changed

  • integrate/authentication/overview.mdx — the RFC 8414 example gains authorization_response_iss_parameter_supported: true, and a new section The authorization response names its issuer (RFC 9207): iss on the ?code= success and the ?error= denial alike, equal to the tier's issuer, compare by plain string equality and reject a mismatch; the device flow has no authorization response.
  • integrate/authentication/oauth-authorization-code.mdx — a prerequisite on which consent you are asking for: agent_id → agent-specific; otherwise account-level, consent_type=commerce for a hand-configured provider (LangSmith Fleet keeps its ?consent_type=commerce), scope=commerce for a discovery-driven client that never saw consent_type; an explicit consent_type wins; consent_type=account_access&scope=commerce or scope=commerce + agent_id is refused, never guessed; scope is not advertised (no scopes_supported, unchanged) and never reaches the API. iss on the hand-off, and the flag in the discovery note.
  • integrate/authentication/for-agents.mdx — the two extra facts the document tells an agent (iss; scope=commerce as the one scope the consent screen honours).
  • integrations/mcp.mdx — a note on consent selection through scope (MCP clients forward the scope from the server's WWW-Authenticate challenge) and on iss, including why ChatGPT cares (stable CIMD client identity).
  • api-reference/oauth-openapi.json (hand-maintained, per the docs CLAUDE.md exception) — the flag in the description and the example, edited as text.

Deliberately not touched: products/catalog/mcp.mdx's "OAuth-connector variant … tracked elsewhere" — that is the Commerce MCP's own challenge/PRM (nvm-monorepo#3459) and the verification matrix (#3460), documented when those land. api-reference/** SDK-sourced trees untouched.

Verification

mintlify broken-linkssuccess no broken links found (run from a worktree off origin/main; no bare <placeholder> in the diff, so the check did run).

🤖 Generated with Claude Code

…ization response carries RFC 9207 `iss`

Companion to nevermined-io/nvm-monorepo#3532 (nvm-monorepo#3527, epic #3458).

- overview: the RFC 8414 example gains `authorization_response_iss_parameter_supported: true`
  and a section on validating `iss` (equal to the tier's `issuer`; success and denial; the
  device flow has no authorization response).
- oauth-authorization-code: a prerequisite on which consent is being asked for — `agent_id`
  vs account-level, `consent_type=commerce` for a hand-configured provider, `scope=commerce`
  for a discovery-driven client, explicit wins, a contradiction is refused; `iss` on the
  hand-off; the flag in the discovery note.
- for-agents: the two extra facts the document tells an agent (`iss`, and `scope=commerce`
  as the one scope the consent screen honours despite no `scopes_supported`).
- integrations/mcp: a note on consent selection through `scope` and on `iss`.
- api-reference/oauth-openapi.json (hand-maintained): the flag in the description + example,
  edited as text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aaitor

aaitor commented Sep 15, 2026

Copy link
Copy Markdown
Member

No description provided.

@aaitor aaitor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current head. One documentation consistency issue is inline.

- **A pre-registered `client_id`.** Same as the device flow — connectors are onboarded out of band (no dynamic client registration). An unregistered `client_id` is rejected with `BCK.OAUTH.0016`.
- **PKCE is mandatory.** Generate a `code_verifier` and its `code_challenge` (`S256`). There is no non-PKCE path.
- **The right tier's authorize URL.** The browser step starts at the `authorization_endpoint` of the tier you are integrating with — `https://nevermined.app/oauth/authorize?network=sandbox` or `…?network=live` — as advertised by that tier's [RFC 8414 document](/integrate/authentication/overview#each-tier-is-its-own-issuer-and-its-authorize-url-says-so). This is the **web app's consent page**, the URL a connector sends the user's browser to with `client_id`, `redirect_uri`, `code_challenge` and the rest in its **query string** — keep the `network` parameter when you add them; it is what makes the consent screen open on the tier your `client_id` is registered on. It is not the API's `POST /oauth/authorize` in Step 1 below, which the signed-in browser client calls on the API host with those same fields in a **JSON body** (and no `network` — by then the tier is fixed). The consent URL is the only one of the two an integrator ever configures by hand; if you do, paste it with its query string.
- **Which consent you are asking for.** An `agent_id` on the authorize URL asks for an agent-specific grant (this page's flow). Without one, the consent is **account-level**, and its kind is named on the URL: `consent_type=commerce` for a spend cap the user funds from their Nevermined wallet for paying external services through the Router; nothing (or `consent_type=account_access`) for a card-backed account mandate. A **discovery-driven** client — one that took the endpoint from the RFC 8414 document and never saw a `consent_type` — selects the commerce consent with the standard OAuth `scope` parameter instead: `scope=commerce` (space-separated per RFC 6749 §3.3; extra scopes such as `offline_access` are tolerated and ignored) is treated as `consent_type=commerce`. An explicit `consent_type` always wins, and the two must not disagree — `consent_type=account_access&scope=commerce`, or `scope=commerce` together with an `agent_id`, is refused with an error rather than resolved silently. A hand-configured provider (LangSmith Fleet's Agent-Auth, say) keeps stating `?consent_type=commerce` on its authorize URL; `scope` is for clients that cannot. `scope` is not advertised in the discovery document (Nevermined publishes no `scopes_supported` — the API layer enforces no scope vocabulary) and never reaches the API: it only selects which consent screen the human sees.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should-fix: This says an explicit consent_type “always wins,” but the next clause and selectConsentType in nvm-monorepo#3532 reject consent_type=account_access with scope=commerce as contradictory. Those are different precedence rules in the same sentence. Please say that the explicit value is preserved only when scope does not contradict it, or simply that contradictions are rejected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants