From ee8121ed79059b95b41c01c45b1d7450ab19d057 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sat, 14 Mar 2026 08:08:33 -0700 Subject: [PATCH 01/16] docs: implement mechanism registry and capability-driven scope negotiation for identity linking --- docs/specification/identity-linking.md | 179 +++++++++++++------------ 1 file changed, 91 insertions(+), 88 deletions(-) diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index c78c7ce3..31261b27 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -28,15 +28,76 @@ This linkage is foundational for commerce experiences, such as accessing loyalty benefits, utilizing personalized offers, managing wishlists, and executing authenticated checkouts. -**This specification leverages -[OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749){ target="_blank" }** as the mechanism -for securely linking a user's platform account with their business account. +**This specification implements a Mechanism Registry pattern**, allowing platforms and businesses to negotiate the authentication mechanism dynamically. While [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749){ target="_blank" } is the primary recommended mechanism, the design natively supports future extensibility securely. -## General guidelines +## Mechanism Registry Pattern -(In addition to the overarching guidelines) +The Identity Linking capability configuration acts as a **registry** of supported authentication mechanisms. Platforms and businesses discover and negotiate the mechanism exactly like other UCP capabilities. -### For platforms +### UCP Capability Declaration + +Businesses **MUST** declare the supported mechanisms in the capability `config` using the `supported_mechanisms` array. Each mechanism must dictate its `type` using an open string vocabulary (e.g., `oauth2`, `verifiable_credential`, `oidc`) and provide the necessary resolution endpoints (like `issuer`). + +```json +{ + "dev.ucp.common.identity_linking": [ + { + "version": "2026-03-14", + "config": { + "supported_mechanisms": [ + { + "type": "oauth2", + "issuer": "https://auth.merchant.example.com" + } + ] + } + } + ] +} +``` + +Platforms **MUST** select the mechanism they support from the `supported_mechanisms` array to proceed with identity linking. + +## Capability-Driven Scope Negotiation (Least Privilege) + +To maintain the **Principle of Least Privilege**, authorization scopes are **NOT** hardcoded or monolithically mandated within the identity linking capability. + +Instead, **authorization scopes are dynamically derived from the final intersection of negotiated capabilities**. + +1. **Schema Declaration:** Each individual capability schema explicitly defines its own required identity scopes (e.g., `dev.ucp.shopping.checkout` declares `ucp:scopes:checkout_session`). +2. **Dynamic Derivation:** During UCP Discovery, when the platform computes the intersection of supported capabilities between itself and the business, it extracts the required scopes from **only** the successfully negotiated capabilities. +3. **Authorization:** The platform initiates the connection requesting **only** the derived scopes. If a capability (e.g., `order`) is excluded from the active capability set, its respective scopes **MUST NOT** be requested by the platform. + +### Scope Structure & Mapping + +The scope complexity should be hidden in the consent screen shown to the user: they shouldn't see one row for each action, but rather a general one, for example "Allow \[platform\] to manage checkout sessions". A requested scope granting access to a capability must grant access to all operations strictly associated with the capability. + +Example capability-to-scope mapping based on UCP schemas: + +Resources | Operation | Scope Action +:-------------- | :------------------------- | :---------------------------- +CheckoutSession | Get, Create, Update, Delete, Cancel, Complete | `ucp:scopes:checkout_session` + +## Supported Mechanisms + +### OAuth 2.0 (`"type": "oauth2"`) + +When the negotiated mechanism type is `oauth2`, platforms and businesses **MUST** adhere to the following standard parameters. + +#### Discovery Bridging +When a platform encounters `"type": "oauth2"`, it **MUST** parse the `issuer` string provided in the config and seamlessly execute an [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414){target="_blank"} discovery fetch by appending `/.well-known/oauth-authorization-server` to the issuer. + +Example metadata retrieved via RFC 8414: +```json +{ + "issuer": "https://auth.merchant.example.com", + "authorization_endpoint": "https://auth.merchant.example.com/oauth2/authorize", + "token_endpoint": "https://auth.merchant.example.com/oauth2/token", + "revocation_endpoint": "https://auth.merchant.example.com/oauth2/revoke" +} +``` + +#### For platforms * **MUST** authenticate using their `client_id` and `client_secret` ([RFC 6749 2.3.1](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1){target="_blank"}) @@ -53,10 +114,7 @@ for securely linking a user's platform account with their business account. as the primary linking mechanism. * **SHOULD** include a unique, unguessable state parameter in the authorization request to prevent Cross-Site Request Forgery (CSRF) - ([RFC 6749 10.12](https://datatracker.ietf.org/doc/html/rfc6749#section-10.12){target="_blank"}) - (part of - [OAuth 2.1 draft](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-14#name-preventing-csrf-attacks){target="_blank"}) - . + ([RFC 6749 10.12](https://datatracker.ietf.org/doc/html/rfc6749#section-10.12){target="_blank"}). * Revocation and security events * **SHOULD** call the business's revocation endpoint ([RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009){target="_blank"}) when a user @@ -66,96 +124,41 @@ for securely linking a user's platform account with their business account. to handle asynchronous account updates, unlinking events, and cross-account protection. -### For businesses +#### For businesses * **MUST** implement OAuth 2.0 ([RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749)) * **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** implement - [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728/) (HTTP - Resource Metadata) to allow platforms to discover the Authorization - Server associated with specific resources. - * **SHOULD** fill in `scopes_supported` as part of - [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414). * **MUST** enforce Client Authentication at the Token Endpoint. * **MUST** provide an account creation flow if the user does not already have an account. -* **MUST** support standard UCP scopes, as defined in the Scopes section, - granting the tokens permission to all associated Operations for a given - resource. -* Additional permissions **MAY** be granted beyond those explicitly requested, - provided that the requested scopes are, at minimum, included. -* The platform and business **MAY** define additional custom scopes beyond the - minimum scope requirements. +* **MUST** support dynamically requested UCP scopes mapped strictly to the capabilities actively negotiated in the session. * Revocation and security events * **MUST** implement standard Token Revocation as defined in [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009). * **MUST** revoke the specified token and **SHOULD** recursively revoke - all associated tokens (e.g., revoking a `refresh_token` **MUST** also - immediately revoke all active `access_token`s issued from it). - * **MUST** support revocation requests authenticated with the same client - credentials used for the token endpoint. + all associated tokens. * **SHOULD** support [OpenID RISC Profile 1.0](https://openid.net/specs/openid-risc-1_0-final.html) - to enable Cross-Account Protection and securely signal revocation or - account state changes initiated by the business side. - ([See Cross-Account protection](https://developers.google.com/identity/account-linking/unlinking#cross-account_protection_risc)) - -## Scopes - -We'd ask users to authorize the platform to have access to all the scopes that -could be required for UCP, regardless of whether the business supports them. - -### Structure - -The scope complexity should be hidden in the consent screen shown to the user: -they shouldn't see one row for each action, but rather a general one, for -example "Allow \[platform\] to manage checkout sessions". - -### Mapping between resources, actions and capabilities - -Resources | Operation | Scope Action -:-------------- | :------------------------- | :---------------------------- -CheckoutSession | Get | `ucp:scopes:checkout_session` -CheckoutSession | Create | `ucp:scopes:checkout_session` -CheckoutSession | Update | `ucp:scopes:checkout_session` -CheckoutSession | Delete | `ucp:scopes:checkout_session` -CheckoutSession | Cancel | `ucp:scopes:checkout_session` -CheckoutSession | Complete | `ucp:scopes:checkout_session` - -A scope covering a capability must grant access to all operations associated to -the capability. For example, ucp:scopes:checkout\_session must grant all of: -Get, Create, Update, Delete, Cancel, Complete. - -## Examples - -### Authorization server metadata - -Example of [metadata](https://datatracker.ietf.org/doc/html/rfc8414#section-2){target="_blank"} -supposed to be hosted in /.well-known/oauth-authorization-server as per -[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414){target="_blank"}: - -```json -{ - "issuer": "https://merchant.example.com", - "authorization_endpoint": "https://merchant.example.com/oauth2/authorize", - "token_endpoint": "https://merchant.example.com/oauth2/token", - "revocation_endpoint": "https://merchant.example.com/oauth2/revoke", - "scopes_supported": [ - "ucp:scopes:checkout_session", - ], - "response_types_supported": [ - "code" - ], - "grant_types_supported": [ - "authorization_code", - "refresh_token" - ], - "token_endpoint_auth_methods_supported": [ - "client_secret_basic" - ], - "service_documentation": "https://merchant.example.com/docs/oauth2" -} -``` + to enable Cross-Account Protection. + +## End-to-End Workflow & Example + +### Scenario: An AI Shopping Agent Checking Out + +1. **Profile Discovery & Capability Negotiation**: The agent fetches the merchant's `/.well-known/ucp` profile. The agent intersects its own profile with the business's and successfully negotiates `dev.ucp.shopping.checkout` and `dev.ucp.common.identity_linking`. If the business supported `dev.ucp.shopping.order`, but the agent did not, it is excluded. +2. **Schema Fetch & Scope Derivation**: The agent parses the schema logic for `dev.ucp.shopping.checkout` and derives that the required scope is strictly `ucp:scopes:checkout_session`. `ucp:scopes:order_management` is strictly omitted. +3. **Identity Mechanism Execution**: Because `identity_linking` matched and defined mechanism `type: oauth2` with issuer `https://auth.merchant.example.com`, the agent executes standard OAuth discovery by appending `/.well-known/oauth-authorization-server` to the issuer string. +4. **User Consent & Authorization**: The agent generates a consent URL to prompt the user (or invokes the authorization flow directly in the GUI), using the dynamically derived scopes. + ```http + GET https://auth.merchant.example.com/oauth2/authorize + ?response_type=code + &client_id=agent_client_123 + &redirect_uri=https://agent.example.com/callback + &scope=ucp:scopes:checkout_session + &state=xyz123 + ``` + *The user is prompted to consent **only** to "Manage Checkout Sessions".* +5. **Authorized UCP Execution**: The platform securely exchanges the authorization code for an `access_token` bound only to checkout and successfully utilizes the UCP REST APIs via `Authorization: Bearer `. From 09e54bfb010e252781ab01088cd89d8bd9163a6a Mon Sep 17 00:00:00 2001 From: amithanda Date: Sat, 14 Mar 2026 08:17:17 -0700 Subject: [PATCH 02/16] docs: add capability profile examples and schema for identity linking discovery --- docs/specification/identity-linking.md | 233 ++++++++++++-------- docs/specification/overview.md | 22 ++ source/schemas/common/identity_linking.json | 75 +++++++ 3 files changed, 239 insertions(+), 91 deletions(-) create mode 100644 source/schemas/common/identity_linking.json diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index 31261b27..862a42b2 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -16,7 +16,7 @@ # Identity Linking Capability -* **Capability Name:** `dev.ucp.common.identity_linking` +- **Capability Name:** `dev.ucp.common.identity_linking` ## Overview @@ -24,141 +24,192 @@ The Identity Linking capability enables a **platform** (e.g., Google, an agentic service) to obtain authorization to perform actions on behalf of a user on a **business**'s site. -This linkage is foundational for commerce experiences, such as accessing -loyalty benefits, utilizing personalized offers, managing wishlists, and -executing authenticated checkouts. +This linkage is foundational for commerce experiences, such as accessing loyalty +benefits, utilizing personalized offers, managing wishlists, and executing +authenticated checkouts. -**This specification implements a Mechanism Registry pattern**, allowing platforms and businesses to negotiate the authentication mechanism dynamically. While [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749){ target="_blank" } is the primary recommended mechanism, the design natively supports future extensibility securely. +**This specification implements a Mechanism Registry pattern**, allowing +platforms and businesses to negotiate the authentication mechanism dynamically. +While +OAuth +2.0 is the primary recommended mechanism, the design natively supports +future extensibility securely. ## Mechanism Registry Pattern -The Identity Linking capability configuration acts as a **registry** of supported authentication mechanisms. Platforms and businesses discover and negotiate the mechanism exactly like other UCP capabilities. +The Identity Linking capability configuration acts as a **registry** of +supported authentication mechanisms. Platforms and businesses discover and +negotiate the mechanism exactly like other UCP capabilities. ### UCP Capability Declaration -Businesses **MUST** declare the supported mechanisms in the capability `config` using the `supported_mechanisms` array. Each mechanism must dictate its `type` using an open string vocabulary (e.g., `oauth2`, `verifiable_credential`, `oidc`) and provide the necessary resolution endpoints (like `issuer`). +Businesses **MUST** declare the supported mechanisms in the capability `config` +using the `supported_mechanisms` array. Each mechanism must dictate its `type` +using an open string vocabulary (e.g., `oauth2`, `verifiable_credential`) and provide the necessary resolution endpoints (like `issuer`). ```json { - "dev.ucp.common.identity_linking": [ - { - "version": "2026-03-14", - "config": { - "supported_mechanisms": [ - { - "type": "oauth2", - "issuer": "https://auth.merchant.example.com" - } - ] - } - } - ] + "dev.ucp.common.identity_linking": [ + { + "version": "2026-03-14", + "config": { + "supported_mechanisms": [ + { + "type": "oauth2", + "issuer": "https://auth.merchant.example.com" + } + ] + } + } + ] } ``` -Platforms **MUST** select the mechanism they support from the `supported_mechanisms` array to proceed with identity linking. +Platforms **MUST** select the mechanism they support from the +`supported_mechanisms` array to proceed with identity linking. ## Capability-Driven Scope Negotiation (Least Privilege) -To maintain the **Principle of Least Privilege**, authorization scopes are **NOT** hardcoded or monolithically mandated within the identity linking capability. +To maintain the **Principle of Least Privilege**, authorization scopes are +**NOT** hardcoded within the identity linking capability. -Instead, **authorization scopes are dynamically derived from the final intersection of negotiated capabilities**. +Instead, **authorization scopes are dynamically derived from the final +intersection of negotiated capabilities**. -1. **Schema Declaration:** Each individual capability schema explicitly defines its own required identity scopes (e.g., `dev.ucp.shopping.checkout` declares `ucp:scopes:checkout_session`). -2. **Dynamic Derivation:** During UCP Discovery, when the platform computes the intersection of supported capabilities between itself and the business, it extracts the required scopes from **only** the successfully negotiated capabilities. -3. **Authorization:** The platform initiates the connection requesting **only** the derived scopes. If a capability (e.g., `order`) is excluded from the active capability set, its respective scopes **MUST NOT** be requested by the platform. +1. **Schema Declaration:** Each individual capability schema explicitly defines + its own required identity scopes (e.g., `dev.ucp.shopping.checkout` declares + `ucp:scopes:checkout_session`). +2. **Dynamic Derivation:** During UCP Discovery, when the platform computes the + intersection of supported capabilities between itself and the business, it + extracts the required scopes from **only** the successfully negotiated + capabilities. +3. **Authorization:** The platform initiates the connection requesting **only** + the derived scopes. If a capability (e.g., `order`) is excluded from the + active capability set, its respective scopes **MUST NOT** be requested by the + platform. ### Scope Structure & Mapping -The scope complexity should be hidden in the consent screen shown to the user: they shouldn't see one row for each action, but rather a general one, for example "Allow \[platform\] to manage checkout sessions". A requested scope granting access to a capability must grant access to all operations strictly associated with the capability. +The scope complexity should be hidden in the consent screen shown to the user: +they shouldn't see one row for each action, but rather a general one, for +example "Allow \[platform\] to manage checkout sessions". A requested scope +granting access to a capability must grant access to all operations strictly +associated with the capability. Example capability-to-scope mapping based on UCP schemas: -Resources | Operation | Scope Action -:-------------- | :------------------------- | :---------------------------- -CheckoutSession | Get, Create, Update, Delete, Cancel, Complete | `ucp:scopes:checkout_session` +| Resources | Operation | Scope Action | +| :-------------- | :-------------------------------------------- | :---------------------------- | +| CheckoutSession | Get, Create, Update, Delete, Cancel, Complete | `ucp:scopes:checkout_session` | ## Supported Mechanisms ### OAuth 2.0 (`"type": "oauth2"`) -When the negotiated mechanism type is `oauth2`, platforms and businesses **MUST** adhere to the following standard parameters. +When the negotiated mechanism type is `oauth2`, platforms and businesses +**MUST** adhere to the following standard parameters. #### Discovery Bridging -When a platform encounters `"type": "oauth2"`, it **MUST** parse the `issuer` string provided in the config and seamlessly execute an [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414){target="_blank"} discovery fetch by appending `/.well-known/oauth-authorization-server` to the issuer. + +When a platform encounters `"type": "oauth2"`, it **MUST** parse the `issuer` +string provided in the config and seamlessly execute an +RFC +8414 discovery fetch by appending `/.well-known/oauth-authorization-server` +to the issuer. Example metadata retrieved via RFC 8414: + ```json { - "issuer": "https://auth.merchant.example.com", - "authorization_endpoint": "https://auth.merchant.example.com/oauth2/authorize", - "token_endpoint": "https://auth.merchant.example.com/oauth2/token", - "revocation_endpoint": "https://auth.merchant.example.com/oauth2/revoke" + "issuer": "https://auth.merchant.example.com", + "authorization_endpoint": "https://auth.merchant.example.com/oauth2/authorize", + "token_endpoint": "https://auth.merchant.example.com/oauth2/token", + "revocation_endpoint": "https://auth.merchant.example.com/oauth2/revoke" } ``` #### For platforms -* **MUST** authenticate using their `client_id` and `client_secret` - ([RFC 6749 2.3.1](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1){target="_blank"}) - through HTTP Basic Authentication - ([RFC 7617](https://datatracker.ietf.org/doc/html/rfc7617){target="_blank"}) - when exchanging codes for tokens. - * **MAY** support Client Metadata - * **MAY** support Dynamic Client Registration mechanisms to supersede - static credential exchange. -* The platform must include the token in the HTTP Authorization header using - the Bearer schema (`Authorization: Bearer `) -* **MUST** implement the OAuth 2.0 Authorization Code flow - ([RFC 6749 4.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1){target="_blank"}) - as the primary linking mechanism. -* **SHOULD** include a unique, unguessable state parameter in the - authorization request to prevent Cross-Site Request Forgery (CSRF) - ([RFC 6749 10.12](https://datatracker.ietf.org/doc/html/rfc6749#section-10.12){target="_blank"}). -* Revocation and security events - * **SHOULD** call the business's revocation endpoint - ([RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009){target="_blank"}) when a user - initiates an unlink action on the platform side. - * **SHOULD** support - [OpenID RISC Profile 1.0](https://openid.net/specs/openid-risc-1_0-final.html) - to handle asynchronous account updates, unlinking events, and - cross-account protection. +- **MUST** authenticate using their `client_id` and `client_secret` + (RFC + 6749 2.3.1) through HTTP Basic Authentication + (RFC + 7617) when exchanging codes for tokens. + - **MAY** support Client Metadata + - **MAY** support Dynamic Client Registration mechanisms to supersede static + credential exchange. +- The platform must include the token in the HTTP Authorization header using the + Bearer schema (`Authorization: Bearer `) +- **MUST** implement the OAuth 2.0 Authorization Code flow + (RFC + 6749 4.1) as the primary linking mechanism. +- **SHOULD** include a unique, unguessable state parameter in the authorization + request to prevent Cross-Site Request Forgery (CSRF) + (RFC + 6749 10.12). +- Revocation and security events + - **SHOULD** call the business's revocation endpoint + (RFC + 7009) when a user initiates an unlink action on the platform side. + - **SHOULD** support + [OpenID RISC Profile 1.0](https://openid.net/specs/openid-risc-1_0-final.html) + to handle asynchronous account updates, unlinking events, and + cross-account protection. #### For businesses -* **MUST** implement OAuth 2.0 - ([RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749)) -* **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`) -* **MUST** enforce Client Authentication at the Token Endpoint. -* **MUST** provide an account creation flow if the user does not already have - an account. -* **MUST** support dynamically requested UCP scopes mapped strictly to the capabilities actively negotiated in the session. -* Revocation and security events - * **MUST** implement standard Token Revocation as defined in - [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009). - * **MUST** revoke the specified token and **SHOULD** recursively revoke - all associated tokens. - * **SHOULD** support - [OpenID RISC Profile 1.0](https://openid.net/specs/openid-risc-1_0-final.html) - to enable Cross-Account Protection. +- **MUST** implement OAuth 2.0 + ([RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749)) +- **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`) +- **MUST** enforce Client Authentication at the Token Endpoint. +- **MUST** provide an account creation flow if the user does not already have an + account. +- **MUST** support dynamically requested UCP scopes mapped strictly to the + capabilities actively negotiated in the session. +- Revocation and security events + - **MUST** implement standard Token Revocation as defined in + [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009). + - **MUST** revoke the specified token and **SHOULD** recursively revoke all + associated tokens. + - **SHOULD** support + [OpenID RISC Profile 1.0](https://openid.net/specs/openid-risc-1_0-final.html) + to enable Cross-Account Protection. ## End-to-End Workflow & Example ### Scenario: An AI Shopping Agent Checking Out -1. **Profile Discovery & Capability Negotiation**: The agent fetches the merchant's `/.well-known/ucp` profile. The agent intersects its own profile with the business's and successfully negotiates `dev.ucp.shopping.checkout` and `dev.ucp.common.identity_linking`. If the business supported `dev.ucp.shopping.order`, but the agent did not, it is excluded. -2. **Schema Fetch & Scope Derivation**: The agent parses the schema logic for `dev.ucp.shopping.checkout` and derives that the required scope is strictly `ucp:scopes:checkout_session`. `ucp:scopes:order_management` is strictly omitted. -3. **Identity Mechanism Execution**: Because `identity_linking` matched and defined mechanism `type: oauth2` with issuer `https://auth.merchant.example.com`, the agent executes standard OAuth discovery by appending `/.well-known/oauth-authorization-server` to the issuer string. -4. **User Consent & Authorization**: The agent generates a consent URL to prompt the user (or invokes the authorization flow directly in the GUI), using the dynamically derived scopes. - ```http - GET https://auth.merchant.example.com/oauth2/authorize - ?response_type=code - &client_id=agent_client_123 - &redirect_uri=https://agent.example.com/callback - &scope=ucp:scopes:checkout_session - &state=xyz123 - ``` - *The user is prompted to consent **only** to "Manage Checkout Sessions".* -5. **Authorized UCP Execution**: The platform securely exchanges the authorization code for an `access_token` bound only to checkout and successfully utilizes the UCP REST APIs via `Authorization: Bearer `. +1. **Profile Discovery & Capability Negotiation**: The agent fetches the + merchant's `/.well-known/ucp` profile. The agent intersects its own profile + with the business's and successfully negotiates `dev.ucp.shopping.checkout` + and `dev.ucp.common.identity_linking`. If the business supported + `dev.ucp.shopping.order`, but the agent did not, it is excluded. +2. **Schema Fetch & Scope Derivation**: The agent parses the schema logic for + `dev.ucp.shopping.checkout` and derives that the required scope is strictly + `ucp:scopes:checkout_session`. `ucp:scopes:order_management` is strictly + omitted. +3. **Identity Mechanism Execution**: Because `identity_linking` matched and + defined mechanism `type: oauth2` with issuer + `https://auth.merchant.example.com`, the agent executes standard OAuth + discovery by appending `/.well-known/oauth-authorization-server` to the + issuer string. +4. **User Consent & Authorization**: The agent generates a consent URL to prompt + the user (or invokes the authorization flow directly in the GUI), using the + dynamically derived scopes. + + ```http + GET https://auth.merchant.example.com/oauth2/authorize + ?response_type=code + &client_id=agent_client_123 + &redirect_uri=https://agent.example.com/callback + &scope=ucp:scopes:checkout_session + &state=xyz123 + ``` + + _The user is prompted to consent **only** to "Manage Checkout Sessions"._ +5. **Authorized UCP Execution**: The platform securely exchanges the + authorization code for an `access_token` bound only to checkout and + successfully utilizes the UCP REST APIs via + `Authorization: Bearer `. diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 44c0ce59..123a8cad 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -417,6 +417,21 @@ Businesses publish their profile at `/.well-known/ucp`. An example: "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/discount.json", "extends": "dev.ucp.shopping.checkout" } + ], + "dev.ucp.common.identity_linking": [ + { + "version": "{{ ucp_version }}", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/identity-linking", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/common/identity_linking.json", + "config": { + "supported_mechanisms": [ + { + "type": "oauth2", + "issuer": "https://auth.merchant.example.com" + } + ] + } + } ] }, "payment_handlers": { @@ -518,6 +533,13 @@ example: "webhook_url": "https://platform.example.com/webhooks/ucp/orders" } } + ], + "dev.ucp.common.identity_linking": [ + { + "version": "{{ ucp_version }}", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/identity-linking", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/common/identity_linking.json" + } ] }, "payment_handlers": { diff --git a/source/schemas/common/identity_linking.json b/source/schemas/common/identity_linking.json new file mode 100644 index 00000000..13266bfc --- /dev/null +++ b/source/schemas/common/identity_linking.json @@ -0,0 +1,75 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/{{ ucp_version }}/schemas/common/identity_linking.json", + "name": "dev.ucp.common.identity_linking", + "version": "{{ ucp_version }}", + "title": "Identity Linking Capability", + "description": "Schema for authenticating and establishing verified connections between platforms and businesses.", + + "$defs": { + "platform_schema": { + "allOf": [ + { "$ref": "../capability.json#/$defs/platform_schema" }, + { + "type": "object", + "properties": { + "config": { + "type": "object", + "properties": { + "supported_mechanisms": { + "type": "array", + "items": { "$ref": "#/$defs/mechanism" }, + "minItems": 1 + } + }, + "required": ["supported_mechanisms"] + } + } + } + ] + }, + + "business_schema": { + "allOf": [ + { "$ref": "../capability.json#/$defs/business_schema" }, + { + "type": "object", + "properties": { + "config": { + "type": "object", + "properties": { + "supported_mechanisms": { + "type": "array", + "items": { "$ref": "#/$defs/mechanism" }, + "minItems": 1 + } + }, + "required": ["supported_mechanisms"] + } + } + } + ] + }, + + "response_schema": { + "allOf": [{ "$ref": "../capability.json#/$defs/response_schema" }] + }, + + "mechanism": { + "type": "object", + "required": ["type"], + "properties": { + "type": { + "type": "string", + "description": "Authentication mechanism type. Well-known values: `oauth2`, `verifiable_credential`." + }, + "issuer": { + "type": "string", + "format": "uri", + "description": "The authorization server or credential issuer URL, typically supporting discovery specifications (e.g. RFC 8414 for oauth2)." + } + }, + "additionalProperties": true + } + } +} From 2d2efb9583935cbe934803baa03397a8ec419d71 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sat, 14 Mar 2026 17:08:31 -0700 Subject: [PATCH 03/16] feat: Add explicit `discovery_endpoint` to OAuth2 identity linking, define a metadata resolution hierarchy, and clarify scope derivation in capability negotiation. --- docs/specification/identity-linking.md | 24 +++++++++++++++------ docs/specification/overview.md | 6 ++++++ source/schemas/common/identity_linking.json | 20 +++++++++++++---- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index 862a42b2..358e6915 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -45,7 +45,8 @@ negotiate the mechanism exactly like other UCP capabilities. Businesses **MUST** declare the supported mechanisms in the capability `config` using the `supported_mechanisms` array. Each mechanism must dictate its `type` -using an open string vocabulary (e.g., `oauth2`, `verifiable_credential`) and provide the necessary resolution endpoints (like `issuer`). +using an open string vocabulary (e.g., `oauth2`, `verifiable_credential`) and +provide the necessary resolution endpoints (like `issuer`). ```json { @@ -111,11 +112,21 @@ When the negotiated mechanism type is `oauth2`, platforms and businesses #### Discovery Bridging -When a platform encounters `"type": "oauth2"`, it **MUST** parse the `issuer` -string provided in the config and seamlessly execute an -RFC -8414 discovery fetch by appending `/.well-known/oauth-authorization-server` -to the issuer. +When a platform encounters `"type": "oauth2"`, it **MUST** parse the capability +configuration and securely locate the Authorization Server metadata. + +Platforms **MUST** implement the following resolution hierarchy to determine the +discovery URL: + +1. **Explicit Endpoint (Highest Priority)**: If the capability configuration + provides a `discovery_endpoint` string, the platform **MUST** fetch metadata + directly from that exact URI. +2. **RFC 8414 Standard Discovery**: If no explicit endpoint is provided, the + platform **MUST** append `/.well-known/oauth-authorization-server` to the + defined `issuer` string and fetch. +3. **OIDC Fallback (Lowest Priority)**: If the RFC 8414 fetch returns a + `404 Not Found`, the platform **MUST** append + `/.well-known/openid-configuration` to the defined `issuer` string and fetch. Example metadata retrieved via RFC 8414: @@ -209,6 +220,7 @@ Example metadata retrieved via RFC 8414: ``` _The user is prompted to consent **only** to "Manage Checkout Sessions"._ + 5. **Authorized UCP Execution**: The platform securely exchanges the authorization code for an `access_token` bound only to checkout and successfully utilizes the UCP REST APIs via diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 123a8cad..c88d6208 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -681,6 +681,12 @@ for a session: 4. **Repeat pruning**: Continue step 3 until no more capabilities are removed (handles transitive extension chains). +5. **Derive Scopes (Final Pass)**: If the negotiated capabilities include + authorization mechanisms (e.g., `dev.ucp.common.identity_linking`), scopes + **MUST ONLY** be derived from the finalized intersection list *after* all + pruning loops have stabilized. Capabilities excluded during pruning MUST NOT + contribute to the derived authorization scopes. + The result is the set of capabilities both parties support at mutually compatible versions, with extension dependencies satisfied. diff --git a/source/schemas/common/identity_linking.json b/source/schemas/common/identity_linking.json index 13266bfc..90d26e5e 100644 --- a/source/schemas/common/identity_linking.json +++ b/source/schemas/common/identity_linking.json @@ -56,17 +56,29 @@ }, "mechanism": { + "oneOf": [ + { "$ref": "#/$defs/oauth2" } + ] + }, + + "oauth2": { "type": "object", - "required": ["type"], + "title": "OAuth 2.0 Mechanism", + "required": ["type", "issuer"], "properties": { "type": { - "type": "string", - "description": "Authentication mechanism type. Well-known values: `oauth2`, `verifiable_credential`." + "const": "oauth2", + "description": "OAuth 2.0 authentication mechanism." }, "issuer": { "type": "string", "format": "uri", - "description": "The authorization server or credential issuer URL, typically supporting discovery specifications (e.g. RFC 8414 for oauth2)." + "description": "The authorization server URL, supporting RFC 8414 discovery." + }, + "discovery_endpoint": { + "type": "string", + "format": "uri", + "description": "Optional explicit URI to the authorization server's metadata (e.g., `/.well-known/openid-configuration`). If omitted, platforms construct discovery paths based on the `issuer`." } }, "additionalProperties": true From 230d5f296fd4c3d11390ac31e67b12ca0ec4b725 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sat, 14 Mar 2026 20:29:00 -0700 Subject: [PATCH 04/16] feat: Introduce and apply `identity_scopes` to capability and checkout schemas for OAuth2 scope derivation. --- source/schemas/capability.json | 7 +++++++ source/schemas/common/identity_linking.json | 4 +--- source/schemas/shopping/checkout.json | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/source/schemas/capability.json b/source/schemas/capability.json index 9eb2eb33..60a8245a 100644 --- a/source/schemas/capability.json +++ b/source/schemas/capability.json @@ -27,6 +27,13 @@ } ], "description": "Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions." + }, + "identity_scopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Optional array of OAuth2 scopes required to access this capability. Used for dynamic scope derivation during identity linking." } } } diff --git a/source/schemas/common/identity_linking.json b/source/schemas/common/identity_linking.json index 90d26e5e..304066f9 100644 --- a/source/schemas/common/identity_linking.json +++ b/source/schemas/common/identity_linking.json @@ -56,9 +56,7 @@ }, "mechanism": { - "oneOf": [ - { "$ref": "#/$defs/oauth2" } - ] + "oneOf": [{ "$ref": "#/$defs/oauth2" }] }, "oauth2": { diff --git a/source/schemas/shopping/checkout.json b/source/schemas/shopping/checkout.json index 7ba93cc5..f35b4c93 100644 --- a/source/schemas/shopping/checkout.json +++ b/source/schemas/shopping/checkout.json @@ -4,6 +4,9 @@ "name": "dev.ucp.shopping.checkout", "title": "Checkout", "description": "Base checkout schema. Extensions compose onto this using allOf.", + "identity_scopes": [ + "ucp:scopes:checkout_session" + ], "type": "object", "required": [ "ucp", From e4d68cf4ee5d7ef92e06b384733467b2218d8726 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sat, 14 Mar 2026 21:20:09 -0700 Subject: [PATCH 05/16] feat: Add identity scope pattern validation, clarify identity linking discovery failure, and introduce scope dependency pruning. --- docs/specification/identity-linking.md | 18 ++++++++++++++++-- docs/specification/overview.md | 16 ++++++++++------ source/schemas/capability.json | 3 ++- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index 358e6915..a454d234 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -120,13 +120,17 @@ discovery URL: 1. **Explicit Endpoint (Highest Priority)**: If the capability configuration provides a `discovery_endpoint` string, the platform **MUST** fetch metadata - directly from that exact URI. + directly from that exact URI. If this fetch fails, the platform **MUST** + abort the discovery process and **MUST NOT** fall back to any other + endpoints. 2. **RFC 8414 Standard Discovery**: If no explicit endpoint is provided, the platform **MUST** append `/.well-known/oauth-authorization-server` to the defined `issuer` string and fetch. 3. **OIDC Fallback (Lowest Priority)**: If the RFC 8414 fetch returns a `404 Not Found`, the platform **MUST** append `/.well-known/openid-configuration` to the defined `issuer` string and fetch. + If this final fetch also fails, the platform **MUST** abort the identity + linking process. Example metadata retrieved via RFC 8414: @@ -135,7 +139,17 @@ Example metadata retrieved via RFC 8414: "issuer": "https://auth.merchant.example.com", "authorization_endpoint": "https://auth.merchant.example.com/oauth2/authorize", "token_endpoint": "https://auth.merchant.example.com/oauth2/token", - "revocation_endpoint": "https://auth.merchant.example.com/oauth2/revoke" + "revocation_endpoint": "https://auth.merchant.example.com/oauth2/revoke", + "scopes_supported": [ + "ucp:scopes:checkout_session" + ], + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ] } ``` diff --git a/docs/specification/overview.md b/docs/specification/overview.md index c88d6208..660c62d6 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -672,14 +672,18 @@ for a session: (latest date). If the set is empty (no mutual version), **exclude** the capability from the intersection. -3. **Prune orphaned extensions**: Remove any capability where `extends` is - set but **none** of its parent capabilities are in the intersection. - - For single-parent extensions (`extends: "string"`): parent must be present - - For multi-parent extensions (`extends: ["a", "b"]`): at least one parent - must be present +3. **Prune orphaned extensions & unauthorized capabilities**: Remove any capability that lacks its required structural or functional dependencies: + - **Structural Dependencies**: Remove any capability where `extends` is + set but **none** of its parent capabilities are in the intersection. + - For single-parent extensions (`extends: "string"`): parent must be present + - For multi-parent extensions (`extends: ["a", "b"]`): at least one parent + must be present + - **Scope Dependencies**: Remove any capability declaring `identity_scopes` + if the intersection does not contain a capability capable of negotiating + authorization (e.g., `dev.ucp.common.identity_linking`). 4. **Repeat pruning**: Continue step 3 until no more capabilities are removed - (handles transitive extension chains). + (handles transitive extension chains and chained scope dependencies). 5. **Derive Scopes (Final Pass)**: If the negotiated capabilities include authorization mechanisms (e.g., `dev.ucp.common.identity_linking`), scopes diff --git a/source/schemas/capability.json b/source/schemas/capability.json index 60a8245a..43142fe8 100644 --- a/source/schemas/capability.json +++ b/source/schemas/capability.json @@ -31,7 +31,8 @@ "identity_scopes": { "type": "array", "items": { - "type": "string" + "type": "string", + "pattern": "^[a-zA-Z0-9.-]+:scopes:[a-zA-Z0-9_.-]+$" }, "description": "Optional array of OAuth2 scopes required to access this capability. Used for dynamic scope derivation during identity linking." } From da3824b9c82eb01dbe99bf26cce5fc6a94a785b4 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sat, 14 Mar 2026 23:36:05 -0700 Subject: [PATCH 06/16] feat: Improve identity linking security by adding PKCE and issuer validation requirements, and remove `identity_scopes` from the capability schema. --- docs/specification/identity-linking.md | 102 +++++++++++++++++++++---- docs/specification/overview.md | 4 +- source/schemas/capability.json | 8 -- 3 files changed, 89 insertions(+), 25 deletions(-) diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index a454d234..9861db6e 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -87,7 +87,9 @@ intersection of negotiated capabilities**. 3. **Authorization:** The platform initiates the connection requesting **only** the derived scopes. If a capability (e.g., `order`) is excluded from the active capability set, its respective scopes **MUST NOT** be requested by the - platform. + platform. If the final derived scope list is completely empty, the platform + **MUST** abort the identity linking process, as there are no secured resources + to authorize. ### Scope Structure & Mapping @@ -120,9 +122,9 @@ discovery URL: 1. **Explicit Endpoint (Highest Priority)**: If the capability configuration provides a `discovery_endpoint` string, the platform **MUST** fetch metadata - directly from that exact URI. If this fetch fails, the platform **MUST** - abort the discovery process and **MUST NOT** fall back to any other - endpoints. + directly from that exact URI. If this fetch fails (e.g., non-2xx HTTP response + or connection timeout), the platform **MUST** abort the discovery process and + **MUST NOT** fall back to any other endpoints. 2. **RFC 8414 Standard Discovery**: If no explicit endpoint is provided, the platform **MUST** append `/.well-known/oauth-authorization-server` to the defined `issuer` string and fetch. @@ -132,6 +134,12 @@ discovery URL: If this final fetch also fails, the platform **MUST** abort the identity linking process. +**Issuer Validation**: Regardless of the discovery method used above, the +platform **MUST** validate that the `issuer` value returned in the metadata +exactly matches the `issuer` string defined in the capability configuration. +Failure to validate the issuer exposes the integration to Mix-Up Attacks and +**MUST** result in an aborted linking process. + Example metadata retrieved via RFC 8414: ```json @@ -168,6 +176,12 @@ Example metadata retrieved via RFC 8414: - **MUST** implement the OAuth 2.0 Authorization Code flow (RFC 6749 4.1) as the primary linking mechanism. +- **MUST** strictly implement Proof Key for Code Exchange (PKCE) + ([RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636)) using the `S256` + challenge method to prevent authorization code interception attacks. +- **MUST** securely validate the `iss` parameter returned in the authorization + response ([RFC 9207](https://www.rfc-editor.org/rfc/rfc9207.html)) to prevent + Mix-Up Attacks. - **SHOULD** include a unique, unguessable state parameter in the authorization request to prevent Cross-Site Request Forgery (CSRF) (RFC @@ -189,6 +203,14 @@ Example metadata retrieved via RFC 8414: to declare the location of their OAuth 2.0 endpoints (`/.well-known/oauth-authorization-server`) - **MUST** enforce Client Authentication at the Token Endpoint. +- **MUST** enforce exact string matching for the `redirect_uri` parameter during + the authorization request to prevent open redirects and token theft. +- **MUST** enforce Proof Key for Code Exchange (PKCE) + ([RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636)) validation at the + Token Endpoint for all authorization code exchanges. +- **MUST** return the `iss` parameter in the authorization response + ([RFC 9207](https://www.rfc-editor.org/rfc/rfc9207.html)) matching the + established issuer string. - **MUST** provide an account creation flow if the user does not already have an account. - **MUST** support dynamically requested UCP scopes mapped strictly to the @@ -204,22 +226,57 @@ Example metadata retrieved via RFC 8414: ## End-to-End Workflow & Example -### Scenario: An AI Shopping Agent Checking Out +### Scenario: An AI Shopping Agent (Platform) and a Shopping Merchant (Business) + +#### 1. The Merchant's Profile (`/.well-known/ucp`) + +The Merchant supports checkout, order management, and secure identity features. -1. **Profile Discovery & Capability Negotiation**: The agent fetches the - merchant's `/.well-known/ucp` profile. The agent intersects its own profile +```json +{ + "dev.ucp.shopping.checkout": [{ "version": "2026-03-14", "config": {} }], + "dev.ucp.shopping.order": [{ "version": "2026-03-14", "config": {} }], + "dev.ucp.common.identity_linking": [{ + "version": "2026-03-14", + "config": { + "supported_mechanisms": [{ + "type": "oauth2", + "issuer": "https://auth.merchant.example.com" + }] + } + }] +} +``` + +#### 2. The AI Agent's Profile + +The AI Shopping Agent only knows how to perform checkouts. It does NOT yet know how to manage existing orders. + +```json +{ + "dev.ucp.shopping.checkout": [{ "version": "2026-03-14" }], + "dev.ucp.common.identity_linking": [{ "version": "2026-03-14" }] +} +``` + +#### 3. Execution Steps + +1. **Capability Discovery & Intersection**: The AI Agent intersects its own profile with the business's and successfully negotiates `dev.ucp.shopping.checkout` - and `dev.ucp.common.identity_linking`. If the business supported - `dev.ucp.shopping.order`, but the agent did not, it is excluded. -2. **Schema Fetch & Scope Derivation**: The agent parses the schema logic for - `dev.ucp.shopping.checkout` and derives that the required scope is strictly - `ucp:scopes:checkout_session`. `ucp:scopes:order_management` is strictly + and `dev.ucp.common.identity_linking`. `dev.ucp.shopping.order` is strictly + excluded because the agent does not support it. +2. **Schema Fetch & Dynamic Scope Derivation**: The agent fetches the JSON Schema + definitions for the **Active Capability List** (`checkout.json` and + `identity_linking.json`). The agent parses the schema logic for + `dev.ucp.shopping.checkout`, looking for the top-level `"identity_scopes"` + annotation, and statically derives that the required scope is strictly + `ucp:scopes:checkout_session`. `ucp:scopes:order_management` is inherently omitted. 3. **Identity Mechanism Execution**: Because `identity_linking` matched and defined mechanism `type: oauth2` with issuer `https://auth.merchant.example.com`, the agent executes standard OAuth - discovery by appending `/.well-known/oauth-authorization-server` to the - issuer string. + discovery (appending `/.well-known/oauth-authorization-server` to the issuer string) + and validates that the returned `issuer` exactly matches. 4. **User Consent & Authorization**: The agent generates a consent URL to prompt the user (or invokes the authorization flow directly in the GUI), using the dynamically derived scopes. @@ -227,10 +284,23 @@ Example metadata retrieved via RFC 8414: ```http GET https://auth.merchant.example.com/oauth2/authorize ?response_type=code - &client_id=agent_client_123 - &redirect_uri=https://agent.example.com/callback + &client_id=shopping_agent_client_123 + &redirect_uri=https://shoppingagent.com/callback &scope=ucp:scopes:checkout_session &state=xyz123 + &code_challenge=code_challenge_123 + &code_challenge_method=S256 + ``` + + The business will respond with the authorization code and the `iss` + parameter per RFC 9207: + + ```http + HTTP/1.1 302 Found + Location: https://shoppingagent.com/callback + ?code=code123 + &state=xyz123 + &iss=https://auth.merchant.example.com ``` _The user is prompted to consent **only** to "Manage Checkout Sessions"._ diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 660c62d6..4705d423 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -689,7 +689,9 @@ for a session: authorization mechanisms (e.g., `dev.ucp.common.identity_linking`), scopes **MUST ONLY** be derived from the finalized intersection list *after* all pruning loops have stabilized. Capabilities excluded during pruning MUST NOT - contribute to the derived authorization scopes. + contribute to the derived authorization scopes. If the final derived scope + list is mathematically empty (no active capabilities request scopes), the + agent **MUST** abort the identity linking process. The result is the set of capabilities both parties support at mutually compatible versions, with extension dependencies satisfied. diff --git a/source/schemas/capability.json b/source/schemas/capability.json index 43142fe8..9eb2eb33 100644 --- a/source/schemas/capability.json +++ b/source/schemas/capability.json @@ -27,14 +27,6 @@ } ], "description": "Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions." - }, - "identity_scopes": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[a-zA-Z0-9.-]+:scopes:[a-zA-Z0-9_.-]+$" - }, - "description": "Optional array of OAuth2 scopes required to access this capability. Used for dynamic scope derivation during identity linking." } } } From 4bcee0e372ccfe1e21525ab61075b335c34c3241 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sun, 15 Mar 2026 00:16:06 -0700 Subject: [PATCH 07/16] docs: Add issuer normalization rule for identity linking and update example markdown. --- docs/specification/identity-linking.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index 9861db6e..ac27cf1a 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -137,6 +137,13 @@ discovery URL: **Issuer Validation**: Regardless of the discovery method used above, the platform **MUST** validate that the `issuer` value returned in the metadata exactly matches the `issuer` string defined in the capability configuration. + +*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). + Failure to validate the issuer exposes the integration to Mix-Up Attacks and **MUST** result in an aborted linking process. @@ -303,7 +310,7 @@ The AI Shopping Agent only knows how to perform checkouts. It does NOT yet know &iss=https://auth.merchant.example.com ``` - _The user is prompted to consent **only** to "Manage Checkout Sessions"._ + *The user is prompted to consent **only** to "Manage Checkout Sessions".* 5. **Authorized UCP Execution**: The platform securely exchanges the authorization code for an `access_token` bound only to checkout and From 54e053e35599e07adeb394681da9eed64c10eef8 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sun, 15 Mar 2026 08:46:50 -0700 Subject: [PATCH 08/16] refactor: Simplify platform schema in identity linking by removing unnecessary properties from the JSON schema --- source/schemas/common/identity_linking.json | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/source/schemas/common/identity_linking.json b/source/schemas/common/identity_linking.json index 304066f9..d864c38b 100644 --- a/source/schemas/common/identity_linking.json +++ b/source/schemas/common/identity_linking.json @@ -8,25 +8,7 @@ "$defs": { "platform_schema": { - "allOf": [ - { "$ref": "../capability.json#/$defs/platform_schema" }, - { - "type": "object", - "properties": { - "config": { - "type": "object", - "properties": { - "supported_mechanisms": { - "type": "array", - "items": { "$ref": "#/$defs/mechanism" }, - "minItems": 1 - } - }, - "required": ["supported_mechanisms"] - } - } - } - ] + "allOf": [{ "$ref": "../capability.json#/$defs/platform_schema" }] }, "business_schema": { From df0a72b7dcc1ddd9caada228c7b1e6a8514e50d1 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sun, 15 Mar 2026 12:21:24 -0700 Subject: [PATCH 09/16] feat: Update identity scopes to use reverse DNS notation and enhance identity linking documentation with mechanism selection algorithm and scope naming conventions. --- docs/index.md | 2 +- docs/specification/identity-linking.md | 71 +++++++++++++++------ source/schemas/common/identity_linking.json | 14 +++- source/schemas/shopping/checkout.json | 3 +- 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/docs/index.md b/docs/index.md index 65a24306..005c141d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -287,7 +287,7 @@ image: assets/banner.png "token_endpoint": "https://example.com/oauth2/token", "revocation_endpoint": "https://example.com/oauth2/revoke", "scopes_supported": [ - "ucp:scopes:checkout_session", + "dev.ucp.shopping.scopes.checkout_session", ], "response_types_supported": [ "code" diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index ac27cf1a..0e277b8a 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -66,8 +66,24 @@ provide the necessary resolution endpoints (like `issuer`). } ``` -Platforms **MUST** select the mechanism they support from the -`supported_mechanisms` array to proceed with identity linking. +### Mechanism Selection Algorithm + +The `supported_mechanisms` array is **ordered by the business's preference** +(index 0 = highest priority). Platforms **MUST** use the following algorithm to +select a mechanism: + +1. Iterate the `supported_mechanisms` array from index 0 (first element). +2. For each entry, check whether the platform supports the declared `type`. +3. Select the **first** entry whose `type` the platform supports and proceed + with that mechanism. +4. If no entry in the array has a `type` the platform supports, the platform + **MUST** abort the identity linking process. The platform **MUST NOT** + attempt a partial or fallback linking flow. + +If the platform supports multiple `type` values that appear in the array, the +business's ordering takes precedence — the platform **MUST** use whichever +supported type appears first in the array, regardless of the platform's own +internal preference. ## Capability-Driven Scope Negotiation (Least Privilege) @@ -79,7 +95,7 @@ intersection of negotiated capabilities**. 1. **Schema Declaration:** Each individual capability schema explicitly defines its own required identity scopes (e.g., `dev.ucp.shopping.checkout` declares - `ucp:scopes:checkout_session`). + `dev.ucp.shopping.scopes.checkout_session`). 2. **Dynamic Derivation:** During UCP Discovery, when the platform computes the intersection of supported capabilities between itself and the business, it extracts the required scopes from **only** the successfully negotiated @@ -99,11 +115,21 @@ example "Allow \[platform\] to manage checkout sessions". A requested scope granting access to a capability must grant access to all operations strictly associated with the capability. +### Scope Naming Convention + +Scopes **MUST** use **reverse DNS dot notation**, consistent with UCP capability +names, to prevent namespace collisions: + +- **UCP-defined scopes:** `dev.ucp..scopes.` (e.g., + `dev.ucp.shopping.scopes.checkout_session`) +- **Third-party scopes:** `.scopes.` (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 | `ucp:scopes:checkout_session` | +| Resources | Operation | Scope Action | +| :-------------- | :-------------------------------------------- | :----------------------------------------- | +| CheckoutSession | Get, Create, Update, Delete, Cancel, Complete | `dev.ucp.shopping.scopes.checkout_session` | ## Supported Mechanisms @@ -127,12 +153,16 @@ discovery URL: **MUST NOT** fall back to any other endpoints. 2. **RFC 8414 Standard Discovery**: If no explicit endpoint is provided, the platform **MUST** append `/.well-known/oauth-authorization-server` to the - defined `issuer` string and fetch. -3. **OIDC Fallback (Lowest Priority)**: If the RFC 8414 fetch returns a - `404 Not Found`, the platform **MUST** append + defined `issuer` string and fetch. If this fetch returns any non-2xx response + other than `404 Not Found` (e.g., `500 Internal Server Error`, `503 Service + Unavailable`), or if a connection timeout or network error occurs, the + platform **MUST** abort the discovery process and **MUST NOT** proceed to the + OIDC fallback. +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 also fails, the platform **MUST** abort the identity - linking process. + If this final fetch returns any non-2xx response or a network error, the + platform **MUST** abort the identity linking process. **Issuer Validation**: Regardless of the discovery method used above, the platform **MUST** validate that the `issuer` value returned in the metadata @@ -156,7 +186,7 @@ Example metadata retrieved via RFC 8414: "token_endpoint": "https://auth.merchant.example.com/oauth2/token", "revocation_endpoint": "https://auth.merchant.example.com/oauth2/revoke", "scopes_supported": [ - "ucp:scopes:checkout_session" + "dev.ucp.shopping.scopes.checkout_session" ], "response_types_supported": [ "code" @@ -277,13 +307,14 @@ The AI Shopping Agent only knows how to perform checkouts. It does NOT yet know `identity_linking.json`). The agent parses the schema logic for `dev.ucp.shopping.checkout`, looking for the top-level `"identity_scopes"` annotation, and statically derives that the required scope is strictly - `ucp:scopes:checkout_session`. `ucp:scopes:order_management` is inherently - omitted. -3. **Identity Mechanism Execution**: Because `identity_linking` matched and - defined mechanism `type: oauth2` with issuer - `https://auth.merchant.example.com`, the agent executes standard OAuth - discovery (appending `/.well-known/oauth-authorization-server` to the issuer string) - and validates that the returned `issuer` exactly matches. + `dev.ucp.shopping.scopes.checkout_session`. `dev.ucp.shopping.scopes.order_management` + is inherently omitted. +3. **Identity Mechanism Selection & Execution**: The agent applies the + Mechanism Selection Algorithm to the business's `supported_mechanisms` array. + The first (and only) entry has `type: oauth2`, which the agent supports, so + it is selected. The agent executes standard OAuth discovery (appending + `/.well-known/oauth-authorization-server` to the issuer string) and validates + that the returned `issuer` exactly matches. 4. **User Consent & Authorization**: The agent generates a consent URL to prompt the user (or invokes the authorization flow directly in the GUI), using the dynamically derived scopes. @@ -293,7 +324,7 @@ The AI Shopping Agent only knows how to perform checkouts. It does NOT yet know ?response_type=code &client_id=shopping_agent_client_123 &redirect_uri=https://shoppingagent.com/callback - &scope=ucp:scopes:checkout_session + &scope=dev.ucp.shopping.scopes.checkout_session &state=xyz123 &code_challenge=code_challenge_123 &code_challenge_method=S256 diff --git a/source/schemas/common/identity_linking.json b/source/schemas/common/identity_linking.json index d864c38b..8c4956a6 100644 --- a/source/schemas/common/identity_linking.json +++ b/source/schemas/common/identity_linking.json @@ -4,7 +4,7 @@ "name": "dev.ucp.common.identity_linking", "version": "{{ ucp_version }}", "title": "Identity Linking Capability", - "description": "Schema for authenticating and establishing verified connections between platforms and businesses.", + "description": "Schema for authenticating and establishing verified connections between platforms and businesses. This schema also defines the 'identity_scopes' annotation convention: any UCP capability schema MAY declare a top-level 'identity_scopes' array listing the OAuth 2.0 scopes required to operate that capability. During UCP discovery, platforms MUST collect identity_scopes from all capabilities in the finalized intersection and derive the authorization scope set from them. Absence of identity_scopes on a capability schema means that capability requires no dedicated authorization scope. The canonical shape of this annotation is defined in $defs/identity_scopes below.", "$defs": { "platform_schema": { @@ -37,6 +37,18 @@ "allOf": [{ "$ref": "../capability.json#/$defs/response_schema" }] }, + "identity_scopes": { + "title": "Identity Scopes Annotation", + "description": "A custom UCP annotation placed at the root of a capability schema to declare the OAuth 2.0 scopes required to operate that capability. Scopes MUST use reverse DNS dot notation to prevent namespace collisions (e.g., 'dev.ucp.shopping.scopes.checkout_session' for UCP-defined scopes, 'com.example.scopes.my_capability' for third-party scopes). Platforms collect this annotation from every capability in the finalized negotiated intersection and use the union as the authorization scope set. This annotation is intentionally a plain JSON array so it is ignored by standard JSON Schema validators — it carries semantic meaning only to UCP-aware tooling. Absence of this annotation on a capability schema means that capability requires no dedicated scope.", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9_\\-]*(\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*)+$" + }, + "uniqueItems": true, + "minItems": 1 + }, + "mechanism": { "oneOf": [{ "$ref": "#/$defs/oauth2" }] }, diff --git a/source/schemas/shopping/checkout.json b/source/schemas/shopping/checkout.json index f35b4c93..d38d2d17 100644 --- a/source/schemas/shopping/checkout.json +++ b/source/schemas/shopping/checkout.json @@ -4,8 +4,9 @@ "name": "dev.ucp.shopping.checkout", "title": "Checkout", "description": "Base checkout schema. Extensions compose onto this using allOf.", + "$comment": "identity_scopes is a UCP annotation processed by UCP-aware tooling during capability negotiation. Its canonical definition and processing rules are specified in the identity_linking capability schema.", "identity_scopes": [ - "ucp:scopes:checkout_session" + "dev.ucp.shopping.scopes.checkout_session" ], "type": "object", "required": [ From 15894f0f0207def33d48cf47ce97db239496a12f Mon Sep 17 00:00:00 2001 From: amithanda Date: Sun, 15 Mar 2026 12:36:33 -0700 Subject: [PATCH 10/16] docs: Clarify identity linking process by updating scope dependency pruning rules and enhancing identity linking schema description with annotation conventions. --- docs/specification/overview.md | 5 ++--- source/schemas/common/identity_linking.json | 11 ++++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 4705d423..2a84afb5 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -679,8 +679,7 @@ for a session: - For multi-parent extensions (`extends: ["a", "b"]`): at least one parent must be present - **Scope Dependencies**: Remove any capability declaring `identity_scopes` - if the intersection does not contain a capability capable of negotiating - authorization (e.g., `dev.ucp.common.identity_linking`). + if `dev.ucp.common.identity_linking` is not present in the intersection. 4. **Repeat pruning**: Continue step 3 until no more capabilities are removed (handles transitive extension chains and chained scope dependencies). @@ -691,7 +690,7 @@ for a session: pruning loops have stabilized. Capabilities excluded during pruning MUST NOT contribute to the derived authorization scopes. If the final derived scope list is mathematically empty (no active capabilities request scopes), the - agent **MUST** abort the identity linking process. + agent **SHOULD** abort the identity linking process. The result is the set of capabilities both parties support at mutually compatible versions, with extension dependencies satisfied. diff --git a/source/schemas/common/identity_linking.json b/source/schemas/common/identity_linking.json index 8c4956a6..8b8dd4f0 100644 --- a/source/schemas/common/identity_linking.json +++ b/source/schemas/common/identity_linking.json @@ -4,7 +4,8 @@ "name": "dev.ucp.common.identity_linking", "version": "{{ ucp_version }}", "title": "Identity Linking Capability", - "description": "Schema for authenticating and establishing verified connections between platforms and businesses. This schema also defines the 'identity_scopes' annotation convention: any UCP capability schema MAY declare a top-level 'identity_scopes' array listing the OAuth 2.0 scopes required to operate that capability. During UCP discovery, platforms MUST collect identity_scopes from all capabilities in the finalized intersection and derive the authorization scope set from them. Absence of identity_scopes on a capability schema means that capability requires no dedicated authorization scope. The canonical shape of this annotation is defined in $defs/identity_scopes below.", + "description": "Schema for authenticating and establishing verified connections between platforms and businesses.", + "$comment": "This schema also owns the 'identity_scopes' annotation convention. Any UCP capability schema MAY declare a top-level 'identity_scopes' array listing the OAuth 2.0 scopes required to operate that capability. During UCP discovery, platforms MUST collect identity_scopes from all capabilities in the finalized intersection and use the union as the authorization scope set. Absence of identity_scopes means the capability requires no dedicated scope. The canonical shape and pattern for this annotation is defined in $defs/identity_scopes.", "$defs": { "platform_schema": { @@ -33,17 +34,13 @@ ] }, - "response_schema": { - "allOf": [{ "$ref": "../capability.json#/$defs/response_schema" }] - }, - "identity_scopes": { "title": "Identity Scopes Annotation", "description": "A custom UCP annotation placed at the root of a capability schema to declare the OAuth 2.0 scopes required to operate that capability. Scopes MUST use reverse DNS dot notation to prevent namespace collisions (e.g., 'dev.ucp.shopping.scopes.checkout_session' for UCP-defined scopes, 'com.example.scopes.my_capability' for third-party scopes). Platforms collect this annotation from every capability in the finalized negotiated intersection and use the union as the authorization scope set. This annotation is intentionally a plain JSON array so it is ignored by standard JSON Schema validators — it carries semantic meaning only to UCP-aware tooling. Absence of this annotation on a capability schema means that capability requires no dedicated scope.", "type": "array", "items": { "type": "string", - "pattern": "^[a-zA-Z0-9][a-zA-Z0-9_\\-]*(\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*)+$" + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9_\\-]*(\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*)*\\.scopes\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*(\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*)*$" }, "uniqueItems": true, "minItems": 1 @@ -70,7 +67,7 @@ "discovery_endpoint": { "type": "string", "format": "uri", - "description": "Optional explicit URI to the authorization server's metadata (e.g., `/.well-known/openid-configuration`). If omitted, platforms construct discovery paths based on the `issuer`." + "description": "Optional explicit URI to the authorization server's metadata (e.g., `https://auth.merchant.example.com/.well-known/openid-configuration`). If omitted, platforms construct discovery paths based on the `issuer`." } }, "additionalProperties": true From 21321a57b8eaf1ad10c84a656c289fbc8f13e328 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sun, 15 Mar 2026 13:02:01 -0700 Subject: [PATCH 11/16] docs: Update identity linking documentation to clarify scope derivation rules and enhance schema definition for authentication mechanisms. --- docs/specification/identity-linking.md | 5 ++++- docs/specification/overview.md | 4 ++-- source/schemas/common/identity_linking.json | 11 ++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index 0e277b8a..6ed5f205 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -104,7 +104,7 @@ intersection of negotiated capabilities**. the derived scopes. If a capability (e.g., `order`) is excluded from the active capability set, its respective scopes **MUST NOT** be requested by the platform. If the final derived scope list is completely empty, the platform - **MUST** abort the identity linking process, as there are no secured resources + **SHOULD** abort the identity linking process, as there are no secured resources to authorize. ### Scope Structure & Mapping @@ -239,6 +239,9 @@ Example metadata retrieved via RFC 8414: - **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 + platforms to detect scope mismatches early, before initiating the authorization + flow. - **MUST** enforce Client Authentication at the Token Endpoint. - **MUST** enforce exact string matching for the `redirect_uri` parameter during the authorization request to prevent open redirects and token theft. diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 2a84afb5..f1c2bbce 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -684,8 +684,8 @@ for a session: 4. **Repeat pruning**: Continue step 3 until no more capabilities are removed (handles transitive extension chains and chained scope dependencies). -5. **Derive Scopes (Final Pass)**: If the negotiated capabilities include - authorization mechanisms (e.g., `dev.ucp.common.identity_linking`), scopes +5. **Derive Scopes (Final Pass)**: If `dev.ucp.common.identity_linking` is + present in the negotiated capabilities, scopes **MUST ONLY** be derived from the finalized intersection list *after* all pruning loops have stabilized. Capabilities excluded during pruning MUST NOT contribute to the derived authorization scopes. If the final derived scope diff --git a/source/schemas/common/identity_linking.json b/source/schemas/common/identity_linking.json index 8b8dd4f0..a85eb332 100644 --- a/source/schemas/common/identity_linking.json +++ b/source/schemas/common/identity_linking.json @@ -47,7 +47,16 @@ }, "mechanism": { - "oneOf": [{ "$ref": "#/$defs/oauth2" }] + "type": "object", + "description": "Base definition for any authentication mechanism. The 'type' field discriminates between known mechanism schemas (e.g., oauth2). Unknown types pass through with only the base requirement, enabling forward-compatible extensibility.", + "required": ["type"], + "properties": { + "type": { + "type": "string", + "description": "The mechanism type discriminator. Known values: 'oauth2'. Specific mechanism schemas constrain this to a constant value." + } + }, + "additionalProperties": true }, "oauth2": { From 3f7ca21b2f12af5601399d97568406c1b98d13d3 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sun, 15 Mar 2026 14:58:19 -0700 Subject: [PATCH 12/16] docs: Refine authentication mechanism schema description for clarity and update scope derivation language in documentation. --- docs/index.md | 2 +- docs/specification/overview.md | 2 +- source/schemas/common/identity_linking.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index 005c141d..ce85a15c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -287,7 +287,7 @@ image: assets/banner.png "token_endpoint": "https://example.com/oauth2/token", "revocation_endpoint": "https://example.com/oauth2/revoke", "scopes_supported": [ - "dev.ucp.shopping.scopes.checkout_session", + "dev.ucp.shopping.scopes.checkout_session" ], "response_types_supported": [ "code" diff --git a/docs/specification/overview.md b/docs/specification/overview.md index f1c2bbce..14aea05c 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -685,7 +685,7 @@ for a session: (handles transitive extension chains and chained scope dependencies). 5. **Derive Scopes (Final Pass)**: If `dev.ucp.common.identity_linking` is - present in the negotiated capabilities, scopes + present in the negotiated capabilities, the authorization scope set **MUST ONLY** be derived from the finalized intersection list *after* all pruning loops have stabilized. Capabilities excluded during pruning MUST NOT contribute to the derived authorization scopes. If the final derived scope diff --git a/source/schemas/common/identity_linking.json b/source/schemas/common/identity_linking.json index a85eb332..c15168f7 100644 --- a/source/schemas/common/identity_linking.json +++ b/source/schemas/common/identity_linking.json @@ -48,7 +48,7 @@ "mechanism": { "type": "object", - "description": "Base definition for any authentication mechanism. The 'type' field discriminates between known mechanism schemas (e.g., oauth2). Unknown types pass through with only the base requirement, enabling forward-compatible extensibility.", + "description": "Base definition for any authentication mechanism. The 'type' field discriminates between known mechanism schemas (e.g., oauth2). Unknown types pass through with only the base requirement, enabling forward-compatible extensibility. Note: this open base schema does not enforce field requirements for known types — use $defs/oauth2 directly to validate an oauth2 mechanism object explicitly.", "required": ["type"], "properties": { "type": { From ebbf0520d2adcc01ce3152dfc0d2e4b40a3988e0 Mon Sep 17 00:00:00 2001 From: amithanda Date: Sun, 15 Mar 2026 15:08:09 -0700 Subject: [PATCH 13/16] docs: Update identity linking documentation to include issuer normalization details and enhance JSON schema by adding required config property. --- docs/specification/identity-linking.md | 3 ++- source/schemas/common/identity_linking.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index 6ed5f205..bd07d4b6 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -317,7 +317,8 @@ The AI Shopping Agent only knows how to perform checkouts. It does NOT yet know The first (and only) entry has `type: oauth2`, which the agent supports, so it is selected. The agent executes standard OAuth discovery (appending `/.well-known/oauth-authorization-server` to the issuer string) and validates - that the returned `issuer` exactly matches. + that the returned `issuer` exactly matches after normalization (trailing slash + stripping). 4. **User Consent & Authorization**: The agent generates a consent URL to prompt the user (or invokes the authorization flow directly in the GUI), using the dynamically derived scopes. diff --git a/source/schemas/common/identity_linking.json b/source/schemas/common/identity_linking.json index c15168f7..d58007da 100644 --- a/source/schemas/common/identity_linking.json +++ b/source/schemas/common/identity_linking.json @@ -29,7 +29,8 @@ }, "required": ["supported_mechanisms"] } - } + }, + "required": ["config"] } ] }, From 086f2cb508320434d4824c4bc6cf3ede2093a0ee Mon Sep 17 00:00:00 2001 From: amithanda Date: Sun, 15 Mar 2026 15:58:36 -0700 Subject: [PATCH 14/16] docs: Enhance consent screen guidelines in identity linking documentation to ensure clear and grouped permission presentation for users. --- docs/specification/identity-linking.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index bd07d4b6..0f94ddfc 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -109,11 +109,15 @@ intersection of negotiated capabilities**. ### Scope Structure & Mapping -The scope complexity should be hidden in the consent screen shown to the user: -they shouldn't see one row for each action, but rather a general one, for -example "Allow \[platform\] to manage checkout sessions". A requested scope -granting access to a capability must grant access to all operations strictly -associated with the capability. +Consent screens **MUST** present permissions to users in clear, human-readable +language that accurately describes what access is being granted. Rather than +listing each individual operation (Get, Create, Update, Delete, etc.) as a +separate line, consent screens **SHOULD** group them under a single +capability-level description (e.g., "Allow \[platform\] to manage checkout +sessions"). This grouping is for readability — it **MUST NOT** reduce the +transparency of what access the user is authorizing. A scope grants access to +all operations associated with the capability and the consent screen must +accurately reflect that. ### Scope Naming Convention From 4811c054f94646be3558b6deca9a822c26925755 Mon Sep 17 00:00:00 2001 From: amithanda Date: Tue, 17 Mar 2026 19:40:32 -0700 Subject: [PATCH 15/16] docs: Clarify authorization process in identity linking documentation by specifying exact scope requests and enforcing strict issuer comparison without normalization. --- docs/specification/identity-linking.md | 37 +++++++++++++++----------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index 0f94ddfc..b1022f51 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -100,10 +100,11 @@ intersection of negotiated capabilities**. intersection of supported capabilities between itself and the business, it extracts the required scopes from **only** the successfully negotiated capabilities. -3. **Authorization:** The platform initiates the connection requesting **only** - the derived scopes. If a capability (e.g., `order`) is excluded from the - active capability set, its respective scopes **MUST NOT** be requested by the - platform. If the final derived scope list is completely empty, the platform +3. **Authorization:** The platform initiates the connection requesting **exactly** + the derived scopes — the union of `identity_scopes` from all capabilities in + the finalized intersection. If a capability (e.g., `order`) is excluded from + the active capability set, its respective scopes **MUST NOT** be requested by + the platform. If the final derived scope list is completely empty, the platform **SHOULD** abort the identity linking process, as there are no secured resources to authorize. @@ -129,6 +130,9 @@ names, to prevent namespace collisions: - **Third-party scopes:** `.scopes.` (e.g., `com.example.loyalty.scopes.points_balance`) +This format strictly adheres to the scope token syntax defined in +[RFC 6749 Section 3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3). + Example capability-to-scope mapping based on UCP schemas: | Resources | Operation | Scope Action | @@ -169,14 +173,18 @@ discovery URL: platform **MUST** abort the identity linking process. **Issuer Validation**: Regardless of the discovery method used above, the -platform **MUST** validate that the `issuer` value returned in the metadata -exactly matches the `issuer` string defined in the capability configuration. - -*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). +platform **MUST** perform an exact string comparison between the `issuer` value +returned in the metadata and the `issuer` string defined in the capability +configuration, as required by +[RFC 8414 Section 3.3](https://datatracker.ietf.org/doc/html/rfc8414#section-3.3). +No normalization (e.g., trailing slash stripping) is permitted — the comparison +**MUST** be an exact string comparison. + +Businesses **MUST** ensure the `issuer` string declared in their UCP capability +configuration exactly matches both the `issuer` field in their authorization +server metadata and the `iss` claim in any issued JWT access tokens. This +guarantees that standard JWT validation libraries, which perform exact string +equality on `iss`, will succeed without modification. Failure to validate the issuer exposes the integration to Mix-Up Attacks and **MUST** result in an aborted linking process. @@ -243,7 +251,7 @@ Example metadata retrieved via RFC 8414: - **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 +- **MUST** populate `scopes_supported` in their RFC 8414 metadata to allow platforms to detect scope mismatches early, before initiating the authorization flow. - **MUST** enforce Client Authentication at the Token Endpoint. @@ -321,8 +329,7 @@ The AI Shopping Agent only knows how to perform checkouts. It does NOT yet know The first (and only) entry has `type: oauth2`, which the agent supports, so it is selected. The agent executes standard OAuth discovery (appending `/.well-known/oauth-authorization-server` to the issuer string) and validates - that the returned `issuer` exactly matches after normalization (trailing slash - stripping). + that the returned `issuer` is an exact string match to the configured value. 4. **User Consent & Authorization**: The agent generates a consent URL to prompt the user (or invokes the authorization flow directly in the GUI), using the dynamically derived scopes. From 3801b263767aa2b4dabeaa2966a80eac47cc5121 Mon Sep 17 00:00:00 2001 From: amithanda Date: Wed, 18 Mar 2026 21:25:46 -0700 Subject: [PATCH 16/16] docs: Update identity linking documentation to enforce mandatory abortion of the process when no secured resources are available and refine scope mapping for CheckoutSession. --- docs/specification/identity-linking.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/specification/identity-linking.md b/docs/specification/identity-linking.md index b1022f51..3cf85784 100644 --- a/docs/specification/identity-linking.md +++ b/docs/specification/identity-linking.md @@ -105,7 +105,7 @@ intersection of negotiated capabilities**. the finalized intersection. If a capability (e.g., `order`) is excluded from the active capability set, its respective scopes **MUST NOT** be requested by the platform. If the final derived scope list is completely empty, the platform - **SHOULD** abort the identity linking process, as there are no secured resources + **MUST** abort the identity linking process, as there are no secured resources to authorize. ### Scope Structure & Mapping @@ -137,7 +137,7 @@ 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` | +| CheckoutSession | Get, Create, Update, Cancel, Complete | `dev.ucp.shopping.scopes.checkout_session` | ## Supported Mechanisms