diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b8011e..9b08b52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `tokenExchange` pre-defined OAuth2 flow key for OAuth 2.0 Token Exchange + ([RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693)), with `tokenUrl` required, + mirroring the proposed OpenAPI addition + [OAI/OpenAPI-Specification#5428](https://github.com/OAI/OpenAPI-Specification/pull/5428) + ## [v1.1.0] - 2023-04-28 ### Changed diff --git a/README.md b/README.md index 745fc69..5b2cccd 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ library can be described, as well as a custom signed URL authentication scheme. | `name` | string | `apiKey` | **REQUIRED.** The name of the header, query, or cookie parameter to be used. | | `in` | string | `apiKey` | **REQUIRED.** The location of the API key (`query` \| `header` \| `cookie`). | | `scheme` | string | `http` | **REQUIRED.** The name of the HTTP Authorization scheme to be used in the [Authorization header as defined in RFC7235](https://tools.ietf.org/html/rfc7235#section-5.1). The values used SHOULD be registered in the [IANA Authentication Scheme registry](https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml). (`basic` \| `bearer` \| `digest` \| `dpop` \| `hoba` \| `mutual` \| `negotiate` \| `oauth` (1.0) \| `privatetoken` \| `scram-sha-1` \| `scram-sha-256` \| `vapid`) | -| `flows` | Map | `oauth2`, `signedUrl` | **REQUIRED.** Scenarios an API client performs to get an access token from the authorization server. For `oauth2` the following keys are pre-defined for the corresponding OAuth flows: `authorizationCode` \| `implicit` \| `password ` \| `clientCredentials`. The OAuth2 Flow Object applies for `oauth2`, the Signed URL Object applies to `signedUrl`. | +| `flows` | Map | `oauth2`, `signedUrl` | **REQUIRED.** Scenarios an API client performs to get an access token from the authorization server. For `oauth2` the following keys are pre-defined for the corresponding OAuth flows: `authorizationCode` \| `implicit` \| `password ` \| `clientCredentials` \| `tokenExchange`. The OAuth2 Flow Object applies for `oauth2`, the Signed URL Object applies to `signedUrl`. | | `openIdConnectUrl` | string | `openIdConnect` | **REQUIRED.** OpenID Connect URL to discover OpenID configuration values. This MUST be in the form of a URL. | The column "Applies to" specifies for which values of `type` the fields only apply. @@ -89,11 +89,20 @@ They are also only required in this context. Based on the [OpenAPI OAuth Flow Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object). Allows configuration of the supported OAuth Flows. +The `tokenExchange` flow corresponds to OAuth 2.0 Token Exchange as defined in +[RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) (proposed for the OpenAPI +OAuth Flows Object in [OAI/OpenAPI-Specification#5428](https://github.com/OAI/OpenAPI-Specification/pull/5428)): +the client presents a token obtained through another scheme or flow (for example an +OpenID Connect identity token) at the `tokenUrl` and receives a different token back, +such as short-lived, scoped credentials for direct data access. +The `subjectTokenScheme` field links the exchange to the scheme that supplies its input +token, making the multi-step flow machine-discoverable; clients MUST use it when present. | Field Name | Type | Description | | ------------------ | ----------------------- | ------------------------------------------------------------ | | `authorizationUrl` | `string` | **REQUIRED** for parent keys: `"implicit"`, `"authorizationCode"`. The authorization URL to be used for this flow. This MUST be in the form of a URL. | -| `tokenUrl` | `string` | **REQUIRED** for parent keys: `"password"`, `"clientCredentials"`, `"authorizationCode"`. The token URL to be used for this flow. This MUST be in the form of a URL. | +| `tokenUrl` | `string` | **REQUIRED** for parent keys: `"password"`, `"clientCredentials"`, `"authorizationCode"`, `"tokenExchange"`. The token URL to be used for this flow. This MUST be in the form of a URL. | +| `subjectTokenScheme` | `string` | Applies to the parent key `"tokenExchange"`. The key of the `auth:schemes` entry whose token the client presents as the RFC 8693 `subject_token` at the `tokenUrl`. RECOMMENDED whenever more than one scheme is declared, so that a generic client can resolve the order of the steps from the document instead of hardcoding it. | | `scopes` | Map<`string`, `string`> | **REQUIRED.** The available scopes for the authentication scheme. A map between the scope name and a short description for it. The map MAY be empty. | | `refreshUrl` | `string` | The URL to be used for obtaining refresh tokens. This MUST be in the form of a URL. | @@ -120,6 +129,8 @@ Definition for a request parameter. ## Examples `auth:schemes` may be referenced identically in a STAC Asset or Link objects. Examples of these two use-cases are provided below. +A complete, focused example of the two-step token-exchange pattern (identity scheme + exchange scheme linked via `subjectTokenScheme`) +is provided in [examples/collection-token-exchange.json](examples/collection-token-exchange.json). ### Schema definitions diff --git a/examples/collection-token-exchange.json b/examples/collection-token-exchange.json new file mode 100644 index 0000000..95a1a25 --- /dev/null +++ b/examples/collection-token-exchange.json @@ -0,0 +1,81 @@ +{ + "type": "Collection", + "stac_version": "1.0.0", + "id": "collection-token-exchange", + "title": "Gated collection — two-step access via OAuth 2.0 Token Exchange", + "description": "Focused example of the two-step access pattern: the STAC metadata (this document) is public, while the data assets live in access-controlled object storage. Step 1: the client signs in via the `oidc` scheme and obtains an identity token. Step 2: it presents that token as the RFC 8693 `subject_token` at the `token_exchange` scheme's `tokenUrl` (declared by `subjectTokenScheme`) and receives short-lived, scoped credentials to read the assets directly. Note that the `oidc` scheme is intentionally not referenced by any `auth:refs`: it exists only as the exchange's input, which is exactly what `subjectTokenScheme` makes machine-discoverable.", + "license": "proprietary", + "stac_extensions": [ + "https://stac-extensions.github.io/authentication/v1.1.0/schema.json" + ], + "auth:schemes": { + "oidc": { + "type": "openIdConnect", + "description": "Step 1 — identity. Sign in via any OIDC client flow (e.g. Authorization Code + PKCE) to obtain an identity token.", + "openIdConnectUrl": "https://auth.example.com/.well-known/openid-configuration" + }, + "token_exchange": { + "type": "oauth2", + "description": "Step 2 — credentials (OAuth 2.0 Token Exchange, RFC 8693). POST the identity token obtained via the `oidc` scheme as `subject_token` to the `tokenUrl`; receive short-lived, scoped credentials for direct reads of this collection's assets. One exchanged credential covers all assets below.", + "flows": { + "tokenExchange": { + "tokenUrl": "https://credentials.example.com/oauth/token", + "subjectTokenScheme": "oidc", + "scopes": { + "read:data": "Read this collection's data assets" + } + } + } + } + }, + "extent": { + "spatial": { + "bbox": [ + [ + -3.9, + 40.3, + -3.5, + 40.6 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2025-01-01T00:00:00Z", + null + ] + ] + } + }, + "links": [ + { + "href": "https://example.com/examples/collection-token-exchange.json", + "rel": "self" + } + ], + "assets": { + "data": { + "href": "https://storage.example.com/private-bucket/data.parquet", + "title": "Data file (access-controlled)", + "type": "application/vnd.apache.parquet", + "roles": [ + "data" + ], + "auth:refs": [ + "token_exchange" + ] + }, + "metadata": { + "href": "https://storage.example.com/private-bucket/metadata.json", + "title": "Table metadata (access-controlled; same exchanged credentials)", + "type": "application/json", + "roles": [ + "metadata" + ], + "auth:refs": [ + "token_exchange" + ] + } + } +} diff --git a/json-schema/schema.json b/json-schema/schema.json index cd051c9..1a79bf1 100644 --- a/json-schema/schema.json +++ b/json-schema/schema.json @@ -298,7 +298,7 @@ "authorizationUrl" ] }, - "^(password|clientCredentials|authorizationCode)*$": { + "^(password|clientCredentials|authorizationCode|tokenExchange)*$": { "required": [ "tokenUrl" ] @@ -375,6 +375,10 @@ "type": "string", "format": "uri" }, + "subjectTokenScheme": { + "title": "Key of the auth:schemes entry whose token is presented as the subject_token (tokenExchange flow)", + "type": "string" + }, "refreshUrl": { "title": "The URL to be used for obtaining refresh tokens", "type": "string",