diff --git a/.linkignore b/.linkignore index 2f00a358..4dd6a783 100644 --- a/.linkignore +++ b/.linkignore @@ -1,2 +1,2 @@ # Ignore ucp.dev dummy links -https://ucp\.dev/specification/reference\?v=2026-01-11 \ No newline at end of file +https://ucp\.dev/specification/reference?v=2026-01-11 diff --git a/docs/index.md b/docs/index.md index 65a24306..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": [ - "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 c78c7ce3..0f94ddfc 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,138 +24,334 @@ 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 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. - -## General guidelines - -(In addition to the overarching guidelines) - -### 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"}) - (part of - [OAuth 2.1 draft](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-14#name-preventing-csrf-attacks){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. - -### 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. -* 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. - * **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"}: +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 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. + +### 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`) and +provide the necessary resolution endpoints (like `issuer`). ```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" + "dev.ucp.common.identity_linking": [ + { + "version": "2026-03-14", + "config": { + "supported_mechanisms": [ + { + "type": "oauth2", + "issuer": "https://auth.merchant.example.com" + } + ] + } + } + ] } ``` + +### 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) + +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**. + +1. **Schema Declaration:** Each individual capability schema explicitly defines + its own required identity scopes (e.g., `dev.ucp.shopping.checkout` declares + `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 + 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 + **SHOULD** abort the identity linking process, as there are no secured resources + to authorize. + +### Scope Structure & Mapping + +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 + +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 | `dev.ucp.shopping.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 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. 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. 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 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 +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. + +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", + "scopes_supported": [ + "dev.ucp.shopping.scopes.checkout_session" + ], + "response_types_supported": [ + "code" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ] +} +``` + +#### For platforms + +- **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. +- **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 + 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`) +- **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. +- **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 + 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 (Platform) and a Shopping Merchant (Business) + +#### 1. The Merchant's Profile (`/.well-known/ucp`) + +The Merchant supports checkout, order management, and secure identity features. + +```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`. `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 + `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 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. + + ```http + GET https://auth.merchant.example.com/oauth2/authorize + ?response_type=code + &client_id=shopping_agent_client_123 + &redirect_uri=https://shoppingagent.com/callback + &scope=dev.ucp.shopping.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".* + +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..14aea05c 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": { @@ -650,14 +672,25 @@ 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 `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). + (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, 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 + list is mathematically empty (no active capabilities request scopes), the + 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 new file mode 100644 index 00000000..d58007da --- /dev/null +++ b/source/schemas/common/identity_linking.json @@ -0,0 +1,86 @@ +{ + "$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.", + "$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": { + "allOf": [{ "$ref": "../capability.json#/$defs/platform_schema" }] + }, + + "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"] + } + }, + "required": ["config"] + } + ] + }, + + "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_\\-]*)*\\.scopes\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*(\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*)*$" + }, + "uniqueItems": true, + "minItems": 1 + }, + + "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. 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": { + "type": "string", + "description": "The mechanism type discriminator. Known values: 'oauth2'. Specific mechanism schemas constrain this to a constant value." + } + }, + "additionalProperties": true + }, + + "oauth2": { + "type": "object", + "title": "OAuth 2.0 Mechanism", + "required": ["type", "issuer"], + "properties": { + "type": { + "const": "oauth2", + "description": "OAuth 2.0 authentication mechanism." + }, + "issuer": { + "type": "string", + "format": "uri", + "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., `https://auth.merchant.example.com/.well-known/openid-configuration`). If omitted, platforms construct discovery paths based on the `issuer`." + } + }, + "additionalProperties": true + } + } +} diff --git a/source/schemas/shopping/checkout.json b/source/schemas/shopping/checkout.json index 5d5bfab3..8e954578 100644 --- a/source/schemas/shopping/checkout.json +++ b/source/schemas/shopping/checkout.json @@ -4,6 +4,10 @@ "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": [ + "dev.ucp.shopping.scopes.checkout_session" + ], "type": "object", "required": [ "ucp",