feat!: redesign identity linking with mechanism registry and capability-driven scopes#265
Conversation
…ation for identity linking
…efine a metadata resolution hierarchy, and clarify scope derivation in capability negotiation.
…t schemas for OAuth2 scope derivation.
… discovery failure, and introduce scope dependency pruning.
…idation requirements, and remove `identity_scopes` from the capability schema.
…necessary properties from the JSON schema
…identity linking documentation with mechanism selection algorithm and scope naming conventions.
…runing rules and enhancing identity linking schema description with annotation conventions.
…on rules and enhance schema definition for authentication mechanisms.
…and update scope derivation language in documentation.
…zation details and enhance JSON schema by adding required config property.
…tion to ensure clear and grouped permission presentation for users.
|
Great design. The mechanism registry pattern is exactly right — hardcoding OAuth as the only path was the main limitation of the previous spec. This pairs well with #264 (attestation extension for eligibility). The two address different surfaces:
But there's also a natural overlap: wallet attestation as an identity mechanism type. Some commerce flows don't need OAuth account linking at all — the wallet is the identity. A business selling token-gated merchandise needs to verify holdings, not link an account. The registry already supports this cleanly: "supported_mechanisms": [
{
"type": "oauth2",
"issuer": "https://auth.merchant.example.com"
},
{
"type": "wallet_attestation",
"provider_jwks": "https://verifier.example.com/.well-known/jwks.json",
"attestation_endpoint": "https://verifier.example.com/v1/attest"
}
]The platform calls the attestation endpoint, receives a signed payload with For scope derivation: a One question on the spec: the Discovery Bridging section (RFC 8414 / OIDC fallback hierarchy) is tightly coupled to OAuth. Would it make sense to scope that hierarchy under the |
| *Issuer Normalization*: To prevent brittle integrations, the platform **MUST** | ||
| normalize both the locally configured `issuer` and the remotely returned `issuer` | ||
| by stripping any trailing slashes (`/`) before performing the exact string match | ||
| comparison (i.e., `https://auth.example.com` and `https://auth.example.com/` | ||
| **MUST** be evaluated as identical). |
There was a problem hiding this comment.
Under RFC 8414 3.3 it mentions that the string must be an identical match.
I understand that the intent to reduce brittle integrations due to a simple slash, but this can have other consequences. In the case of a trailing slash, the exact string would be incldued in the the signed iss claim of the JWT access token, and standard JWT validation libraries will check for equality (not stripping). If UCP strips the slash during discovery and passes the normalized string, the equality will fail.
Can you drop the normalization step? And can we instead add a mandate that the issuer string in UCP must match exactly the metadata, and the iss parameter?
| ### Scope Naming Convention | ||
|
|
||
| Scopes **MUST** use **reverse DNS dot notation**, consistent with UCP capability | ||
| names, to prevent namespace collisions: | ||
|
|
||
| - **UCP-defined scopes:** `dev.ucp.<domain>.scopes.<capability>` (e.g., | ||
| `dev.ucp.shopping.scopes.checkout_session`) | ||
| - **Third-party scopes:** `<reverse-dns>.scopes.<capability>` (e.g., | ||
| `com.example.loyalty.scopes.points_balance`) | ||
|
|
||
| Example capability-to-scope mapping based on UCP schemas: | ||
|
|
||
| | Resources | Operation | Scope Action | | ||
| | :-------------- | :-------------------------------------------- | :----------------------------------------- | | ||
| | CheckoutSession | Get, Create, Update, Delete, Cancel, Complete | `dev.ucp.shopping.scopes.checkout_session` | |
There was a problem hiding this comment.
+1 for the change from colon-separation to reverse DNS. It aligns with what I've seen in PR 250 for eligibility and other UCP changes.
From an OAuth perspective, RFC 6749 3.3 restricts scope tokens to %x21 / %x23-5B / %x5D-7E, which makes dots perfectly valid and widely supported by Authorization Servers.
(Optionally) add a short note mentioning this format strictly adheres to the scope token syntax defined in RFC 6749 Section 3.3.
| 3. **Authorization:** The platform initiates the connection requesting **only** | ||
| the derived scopes. If a capability (e.g., `order`) is excluded from the |
There was a problem hiding this comment.
Are we intentionally pushing platforms toward incremental authorization here? If a platform negotiates both checkout and order, but only needs checkout for the current session, requesting a subset of scopes means they'll have to trigger a re-linking flow later for order management. This introduces UX friction compared to requesting the full negotiated union upfront.
To clarify the intent, I recommend replacing "only" with either "at most" (if we want to allow subsets for progressive consent) or "exactly" (if we expect platforms to request the full derived set to minimize re-prompting).
| - **MUST** adhere to [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) | ||
| to declare the location of their OAuth 2.0 endpoints | ||
| (`/.well-known/oauth-authorization-server`) | ||
| - **SHOULD** populate `scopes_supported` in their RFC 8414 metadata to allow |
There was a problem hiding this comment.
Should this be MUST? Given that the Platform will only request the derived scopes.
| 3. **OIDC Fallback (Lowest Priority)**: If and only if the RFC 8414 fetch | ||
| returns exactly `404 Not Found`, the platform **MUST** append | ||
| `/.well-known/openid-configuration` to the defined `issuer` string and fetch. | ||
| If this final fetch returns any non-2xx response or a network error, the | ||
| platform **MUST** abort the identity linking process. |
There was a problem hiding this comment.
no actionable comment, just wanted to mention that having this as a fallback is a great idea
| - **MUST** strictly implement Proof Key for Code Exchange (PKCE) | ||
| ([RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636)) using the `S256` |
There was a problem hiding this comment.
This seems like an extension going towards what OAuth 2.1 is pushing for. OAuth 2.0 doesn't specifiy it, and OAuth 2.1 isn't, as of today, an official RFC. Do you want to already include PKCE as an extension of UCP or hold off and add it later? Oauth 2.1 Draft
I suspect this may be significant overhead, especially as a MUST, for platforms and businesses while it's not-quite-yet industry standard.
There was a problem hiding this comment.
Feel free to dismiss this one. PKCE S256 is supported more widely than I anticipated.
cusell-google
left a comment
There was a problem hiding this comment.
This redesign is a massive improvement, left some comments. The only real blocker is related to the issuer normalization. Thanks Amit!
Overview
This PR redesigns the Identity Linking capability from a hardcoded OAuth 2.0
specification into an extensible, security-hardened system built on three
core principles: a Mechanism Registry pattern for future-proof
authentication negotiation, capability-driven least-privilege scope
derivation, and hardened OAuth 2.0 security requirements aligned with
current best practices.
What Changed
Mechanism Registry Pattern (
identity-linking.md,identity_linking.json)The capability no longer hardcodes OAuth 2.0 as the only authentication
mechanism. Instead, businesses declare an ordered
supported_mechanismsarray in their capability config, and platforms select the first mechanism
type they support (business-preference ordering, mirroring TLS cipher suite
negotiation).
A formal Mechanism Selection Algorithm is defined:
typethe platform supportsThis enables future mechanism types (e.g.,
verifiable_credential) withoutbreaking changes to the protocol.
Capability-Driven Least-Privilege Scope Negotiation (
overview.md,checkout.json,identity_linking.json)Authorization scopes are no longer hardcoded or pre-declared in the identity
linking config. Instead:
identity_scopesannotation (e.g.,checkout.jsondeclaresdev.ucp.shopping.scopes.checkout_session)capabilities excluded during negotiation contribute zero scopes
identity_scopesare removed ifdev.ucp.common.identity_linkingis notin the intersection
computed from the stabilized intersection only
This makes over-permissioning impossible by construction, not convention.
Scope Naming Convention
Scopes now use reverse DNS dot notation with a mandatory
.scopes.segment, consistent with UCP capability names:
dev.ucp.<domain>.scopes.<capability>(e.g.,
dev.ucp.shopping.scopes.checkout_session)<reverse-dns>.scopes.<capability>(e.g.,
com.example.loyalty.scopes.points_balance)A regex pattern enforcing this convention is defined in
$defs/identity_scopesinidentity_linking.jsonas the canonicalreference.
Hardened OAuth 2.0 Security (
identity-linking.md)The OAuth 2.0 mechanism requirements are significantly tightened:
S256issvalidation (RFC 9207)redirect_urimatchingDiscovery Resolution Hierarchy (
identity-linking.md)A formal 3-tier hierarchy replaces the previous unspecified discovery:
discovery_endpoint(hard abort on any failure)/.well-known/oauth-authorization-server(only404triggers fallback;
5xx, timeouts abort)/.well-known/openid-configuration(abort on non-2xx)Schema (
identity_linking.json)New schema file with:
platform_schema: passthrough only — platforms are consumers ofmechanisms, not providers
business_schema: requiresconfigwithsupported_mechanisms(
minItems: 1)mechanism: open base type (consistent withpayment_credential.json)—
typerequired,additionalProperties: trueoauth2: named$deffor explicit strict validationidentity_scopes: canonical annotation definition with enforced regexpattern
End-to-End Workflow Example (
identity-linking.md)A complete walkthrough added: AI Shopping Agent (platform) + Merchant
(business), covering discovery, scope derivation, mechanism selection,
OAuth authorization request (with PKCE and
issparameters), and tokenexchange.
Files Changed
docs/specification/identity-linking.mddocs/specification/overview.mdsource/schemas/common/identity_linking.jsonsource/schemas/shopping/checkout.jsonidentity_scopesannotationdocs/index.mdType of change
Please delete options that are not relevant.
Is this a Breaking Change or Removal?
If you checked "Breaking change" above, or if you are removing any schema
files or fields:
!to my PR title (e.g.,feat!: remove field).Checklist