Skip to content
Merged
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ 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
- `auth:refs` allowed on an Authentication Scheme Object, listing the scheme(s) that
may supply this scheme's input — one-of semantics, matching `auth:refs` on Assets
and Links; sequences are expressed as chains of references. Required with at least
one entry for a scheme declaring a `tokenExchange` flow (the token obtained through
whichever referenced scheme was used is the RFC 8693 `subject_token`), so the
multi-step exchange is discoverable from the document; reference chains must be
acyclic
- `subjectTokenType` field (optional) on the `tokenExchange` flow: the RFC 8693
`subject_token_type` URN, for when the referenced scheme yields more than one kind
of token; defaults from the `type` of the referenced scheme used

## [v1.1.0] - 2023-04-28

### Changed
Expand Down
52 changes: 49 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ The fields in the table below can be used in these parts of STAC documents:

| Field Name | Type | Description |
| ----------- | ---------- | ----------- |
| `auth:refs` | \[string\] | A property that specifies which schemes in `auth:schemes` may be used to access an Asset or Link. |
| `auth:refs` | \[string\] | A property that specifies which schemes in `auth:schemes` may be used to access an Asset or Link. `auth:refs` MAY also appear on an [Authentication Scheme Object](#authentication-scheme-object) itself — nested inside `auth:schemes`, so within Catalogs, Collections and Item Properties — where it declares which schemes may supply this scheme's input — see below. |

### Scheme Types

Expand Down Expand Up @@ -76,10 +76,11 @@ library can be described, as well as a custom signed URL authentication scheme.
| ------------------ | ------------------------------------------------------------ | --------------------- | ------------------------------------------------------------ |
| `type` | string | *All* | **REQUIRED**. The authentication scheme type used to access the data (`http` \| `s3` \| `signedUrl` \| `oauth2` \| `apiKey` \| `openIdConnect` \| a custom scheme type ). |
| `description` | string | *All* | Additional instructions for authentication. [CommonMark 0.29](https://commonmark.org/) syntax MAY be used for rich text representation. |
| `auth:refs` | \[string\] | *All* | Keys of other entries in `auth:schemes` that may supply this scheme's input: any *one* of the referenced schemes is completed first, and the token it yields is the input to this one. **REQUIRED with at least one entry** for an `oauth2` scheme declaring a `tokenExchange` flow (the token obtained through whichever referenced scheme was used is the RFC 8693 `subject_token`); MAY be used by other types, e.g. a `signedUrl` scheme naming the scheme(s) whose token authenticates requests to its `authorizationApi`. References MUST NOT form cycles. |
| `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<string, ([OAuth2 Flow Object](#oauth2-flow-object)\|[Signed URL Object](#signed-url-object))> | `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<string, ([OAuth2 Flow Object](#oauth2-flow-object)\|[Signed URL Object](#signed-url-object))> | `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.
Expand All @@ -89,16 +90,59 @@ 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).
The client presents a token obtained through another scheme (for example an OpenID
Connect identity token) at the `tokenUrl` as the `subject_token`, with
`grant_type=urn:ietf:params:oauth:grant-type:token-exchange`, and receives a different
security token back, such as short-lived, scoped credentials for direct data access.
The response is the one defined in
[RFC 8693, Section 2.2.1](https://datatracker.ietf.org/doc/html/rfc8693#section-2.2.1),
which reports the kind of token issued in `issued_token_type`.

Which scheme supplies the input token is declared on the *scheme*, with `auth:refs`
(see the [Authentication Scheme Object](#authentication-scheme-object)): a scheme
declaring a `tokenExchange` flow MUST reference at least one other scheme in
`auth:refs`; the client completes any *one* of the referenced schemes, and the token
obtained through it is the `subject_token`. This is what makes the multi-step flow
machine-discoverable — a client resolves the chain from the document (asset → guarding
scheme → input scheme) instead of hardcoding the order — and it reuses the reference
mechanism clients already implement for Assets and Links, with the same one-of
semantics in both positions: on an Asset or Link the referenced schemes are alternative
ways to access the resource, on a scheme they are alternative ways to obtain its input.
A sequence of steps is expressed as a chain — each scheme referencing the one before
it — not as multiple entries in one list. References MUST NOT form cycles — clients
cannot be expected to resolve one, and the JSON Schema cannot detect it.

The RFC 8693 `subject_token_type` request parameter MUST be taken from the flow's
`subjectTokenType` field when present. When the field is absent it follows from the
`type` of the referenced scheme the client used:
`urn:ietf:params:oauth:token-type:id_token` for `openIdConnect` and
`urn:ietf:params:oauth:token-type:access_token` for `oauth2`. Publishers SHOULD set
`subjectTokenType` explicitly whenever a referenced scheme yields more than one kind
of token (an OpenID Connect provider issues both an ID token and an access token).
Since the field applies to the flow as a whole, referenced alternatives requiring
*different* `subject_token_type` values SHOULD be split into separate exchange
schemes.

To accept identities from more than one provider, list the identity schemes as
alternatives in the exchange scheme's own `auth:refs` — a token obtained through any
one of them is exchanged at the same `tokenUrl`.

| 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. |
| `subjectTokenType` | `string` | For parent key `"tokenExchange"`: the RFC 8693 `subject_token_type` URN of the token presented at the `tokenUrl` (e.g. `urn:ietf:params:oauth:token-type:id_token`). When absent, it follows from the `type` of the scheme in the scheme's `auth:refs` through which the token was obtained. |
| `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. |

### Signed URL Object

A `signedUrl` scheme MAY declare the scheme(s) through which its `authorizationApi`
requests can be authenticated via `auth:refs` on the scheme (see the
[Authentication Scheme Object](#authentication-scheme-object)).

| Field Name | Type | Description |
| ------------------ | -------------------------------------------------- | ------------------------------------------------------------ |
| `method` | `string` | **REQUIRED.** The method to be used for requests |
Expand All @@ -120,6 +164,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 scheme-level `auth:refs`)
is provided in [examples/collection-token-exchange.json](examples/collection-token-exchange.json).

### Schema definitions

Expand Down
81 changes: 81 additions & 0 deletions examples/collection-token-exchange.json
Original file line number Diff line number Diff line change
@@ -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": "Public STAC metadata, access-controlled assets. The asset points at the scheme that guards it (`auth:refs`); that scheme points the same way (scheme-level `auth:refs`) at the identity scheme(s) whose token it exchanges — any one of them. The `oidc` scheme guards no asset on its own: it is the exchange's input.",
"license": "proprietary",
"stac_extensions": [
"https://stac-extensions.github.io/authentication/v1.1.0/schema.json"
],
"auth:schemes": {
"oidc": {
"type": "openIdConnect",
"openIdConnectUrl": "https://auth.example.com/.well-known/openid-configuration"
},
"token_exchange": {
"type": "oauth2",
"description": "RFC 8693 token exchange: present the `oidc` identity token as the `subject_token` at the `tokenUrl` and receive short-lived, scoped credentials covering every asset below.",
"auth:refs": [
"oidc"
],
"flows": {
"tokenExchange": {
"tokenUrl": "https://credentials.example.com/oauth/token",
"subjectTokenType": "urn:ietf:params:oauth:token-type:id_token",
"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",
"type": "application/vnd.apache.parquet",
"roles": [
"data"
],
"auth:refs": [
"token_exchange"
]
},
"metadata": {
"href": "https://storage.example.com/private-bucket/metadata.json",
"type": "application/json",
"roles": [
"metadata"
],
"auth:refs": [
"token_exchange"
]
}
}
}
52 changes: 50 additions & 2 deletions json-schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
}
]
}
],
],
"definitions": {
"schemes_field": {
"type": "object",
Expand Down Expand Up @@ -166,6 +166,13 @@
"description": {
"title": "Authentication scheme description",
"type": "string"
},
"auth:refs": {
"title": "Keys of schemes in auth:schemes that may supply this scheme's input (any one of them is completed first; its result is this scheme's input, e.g. the subject_token of a tokenExchange flow)",
"type": "array",
"items": {
"type": "string"
}
}
},
"allOf": [
Expand Down Expand Up @@ -298,7 +305,7 @@
"authorizationUrl"
]
},
"^(password|clientCredentials|authorizationCode)*$": {
"^(password|clientCredentials|authorizationCode|tokenExchange)*$": {
Comment thread
m-mohr marked this conversation as resolved.
"required": [
"tokenUrl"
]
Expand Down Expand Up @@ -356,6 +363,38 @@
"openIdConnectUrl": false
}
}
},
{
"$comment": "A tokenExchange flow consumes another scheme's token: the scheme must reference at least one scheme; the token obtained through whichever one the client uses is the RFC 8693 subject_token.",
"if": {
"type": "object",
"required": [
"flows"
],
"properties": {
"type": {
"const": "oauth2"
},
"flows": {
"type": "object",
"required": [
"tokenExchange"
]
}
}
},
"then": {
"type": "object",
"required": [
"auth:refs"
],
"properties": {
"auth:refs": {
"type": "array",
"minItems": 1
}
}
}
}
]
},
Expand All @@ -375,6 +414,15 @@
"type": "string",
"format": "uri"
},
"subjectTokenType": {
"title": "RFC 8693 subject_token_type URN of the token presented at the tokenUrl (tokenExchange flow)",
"type": "string",
"format": "uri",
"examples": [
"urn:ietf:params:oauth:token-type:id_token",
"urn:ietf:params:oauth:token-type:access_token"
]
},
"refreshUrl": {
"title": "The URL to be used for obtaining refresh tokens",
"type": "string",
Expand Down
Loading