feat: choose an upstream credential per server on the consent screen
AIM-596 (3 of 5)
Stacked on #8037.
Summary
Applies only in organizations with upstream_auth_enforced. Elsewhere consent keeps choosing through its service cards, unchanged.
-
Effective set (
consentAuthChoices): each server's enabled, associated auth clients, each validated through the same resolver andmcp:authpermitter as serving (resolveAuthClientChoiceForConsent, a typed-client variant of the existing proposal check). Per option:- denied → left out
- unusable → "not set up"
- personal → "connect" or "reconnect" through its consent card
- identity provider → ready once chained
- shared / pass-through → ready
Personal and identity-provider clients are skipped for callers representing nobody. Permission is judged as the consenting person, on the page and again on approval (gateway catalog publication included).
-
Connecting offered personal clients: the consent page lists the registration of every personal or identity-provider client a server offers, whether or not the server's sign-in links it or an older association names it, and reads its connection where the resolver serves it. Gateway discovery fetches a chosen client's tokens the same way.
-
Server states: "No authentication required" (explicit, or undecided with nothing offered), "Authentication setup incomplete" (required, nothing offered), "No access" (everything denied), otherwise a choice. Setup-incomplete and no-access block approval, with a request-access link for the latter. Where the page can also approve for an agent, the same message shows while "Myself" is selected.
-
Preselection: the person's pick if still usable; else the client they last approved for that server (
ListRememberedAuthClients, any of their sessions) if still usable; else the fixed order: identity provider → shared credentials by name → own account → app's own credential. Nothing is preselected that can't be approved now. -
Direct servers: a "Sign in to the service as" radio list in the approve form (a hidden input when there's one option). The POST recomputes the set and refuses a pick that is no longer usable ("Reload this page to choose again"). The grant carries
AuthClientPicks, with the client's own registration inUpstreamClientPicks. -
Gateways: each member row gets a select. Changing it reloads the page with
auth_client.<slug>picks, so the tools captured for approval are those that credential reaches. Discovery uses the member's chosen client, and catalog publication re-validates that typed client rather than the card-derived pick. -
Recording:
RecordUpstreamClientSelectionsrecords a picked typed client with its own registration when the server still offers it enabled, even if the sign-in doesn't bind that registration. A picked headers-only client records none. Existing choices are still never replaced, so later approvals and refresh don't rewrite prior selections.
Not in this PR:
- gateway members added after issuance (next PR)
Motivation
RFC section 7.6: when a server offers several upstream identities the person chooses one per server (gateway: per member), sees "No access" vs setup vs connect correctly, and the choice is recorded per credential. The picker only prefills; issuance validates.