From 7e5f908b8171857615b8e8b10f3ad619b830c868 Mon Sep 17 00:00:00 2001 From: Mehrn0ush Date: Fri, 11 Sep 2026 18:14:25 +0800 Subject: [PATCH 1/4] Declare optional anonymous access alongside Bearer, document 401/403/404 authz behavior on resource operations, and clarify that /token empty security does not allow anonymous client_credentials. Signed-off-by: Mehrn0ush --- auth/readme.md | 12 +++- spec/openapi.yaml | 149 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 157 insertions(+), 4 deletions(-) diff --git a/auth/readme.md b/auth/readme.md index 30b49ac..0702c57 100644 --- a/auth/readme.md +++ b/auth/readme.md @@ -203,7 +203,10 @@ certificate. This restates RFC 6749 section 3.2 and RFC 6750 section 5. ## Client flow Whether a server requires authentication is discovered by using it, not by configuration and not by -probing the token endpoint. The complete flow for a client that does not know in advance: +probing the token endpoint. In the OpenAPI document, resource operations list both Bearer +authentication and an empty security requirement so open and mixed deployments are representable; +a `401` with a `WWW-Authenticate: Bearer` challenge on a given endpoint is still the runtime signal +that a token is required there. The complete flow for a client that does not know in advance: 1. The client sends the resource request it wants, with no credentials. 2. If the server does not require authentication for that endpoint, it answers `200` with the @@ -212,7 +215,9 @@ probing the token endpoint. The complete flow for a client that does not know in the `Bearer` scheme, as described in RFC 6750 section 3. This challenge is the only signal a client needs. 4. The client calls `POST /token` with its credential - for the baseline, the API key over HTTP - Basic - and receives an access token. + Basic - and receives an access token. Unauthenticated `client_credentials` requests are not + permitted; an empty OpenAPI security requirement on `/token` allows alternate client + authentication (for example mutual TLS), not anonymous token issuance. 5. The client repeats the resource request with `Authorization: Bearer `, and presents the same token on subsequent requests until it expires or is rejected. 6. When a later request fails with `401` and `error="invalid_token"`, the client obtains a fresh @@ -261,7 +266,8 @@ authentication: Authorization - which of the protected data an authenticated client may see - is the server's decision and is not constrained by this specification; a client with a valid token may still receive -a filtered view, or `403`/`404` for individual objects. +a filtered view, `403 Forbidden`, or a concealing `404 Not Found` for individual objects. Clients +shall not infer from `404` alone whether the object is absent or withheld. ### Protected resource metadata diff --git a/spec/openapi.yaml b/spec/openapi.yaml index 758804f..aed7cbe 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -37,6 +37,10 @@ paths: $ref: "#/components/schemas/product" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -61,6 +65,10 @@ paths: $ref: "#/components/responses/paginated-product-release" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -85,6 +93,10 @@ paths: "$ref": "#/components/schemas/productRelease" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -109,6 +121,10 @@ paths: "$ref": "#/components/schemas/cle" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -131,6 +147,10 @@ paths: $ref: "#/components/responses/paginated-product-release" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" tags: - TEA Product Release /product/{uuid}/cle: @@ -153,6 +173,10 @@ paths: "$ref": "#/components/schemas/cle" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -176,6 +200,10 @@ paths: $ref: "#/components/responses/paginated-product" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" tags: - TEA Product /component/{uuid}: @@ -198,6 +226,10 @@ paths: "$ref": "#/components/schemas/component" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -222,6 +254,10 @@ paths: $ref: "#/components/responses/paginated-component-release" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -246,6 +282,10 @@ paths: "$ref": "#/components/schemas/cle" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -268,6 +308,10 @@ paths: $ref: "#/components/responses/paginated-component" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" tags: - TEA Component /componentReleases: @@ -288,6 +332,10 @@ paths: $ref: "#/components/responses/paginated-component-release" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" tags: - TEA Component Release /componentRelease/{uuid}: @@ -310,6 +358,10 @@ paths: "$ref": "#/components/schemas/component-release-with-collection" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -334,6 +386,10 @@ paths: "$ref": "#/components/schemas/cle" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -358,6 +414,10 @@ paths: "$ref": "#/components/schemas/collection" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -382,6 +442,10 @@ paths: "$ref": "#/components/schemas/collection" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -406,6 +470,10 @@ paths: $ref: "#/components/responses/paginated-collection" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -430,6 +498,10 @@ paths: $ref: "#/components/responses/paginated-collection" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -460,6 +532,10 @@ paths: "$ref": "#/components/schemas/collection" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -490,6 +566,10 @@ paths: "$ref": "#/components/schemas/collection" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -514,6 +594,10 @@ paths: "$ref": "#/components/schemas/artifact" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -544,6 +628,10 @@ paths: "$ref": "#/components/schemas/artifact" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -576,6 +664,10 @@ paths: $ref: "#/components/responses/artifact-content-redirect" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" '406': @@ -621,6 +713,10 @@ paths: $ref: "#/components/responses/artifact-content-redirect" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" '406': @@ -652,6 +748,10 @@ paths: $ref: "#/components/responses/artifact-content-redirect" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" '406': @@ -703,6 +803,10 @@ paths: $ref: "#/components/responses/artifact-content-redirect" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" '406': @@ -733,6 +837,12 @@ paths: The access token is opaque to the client: clients shall not inspect, parse, or depend on its contents. + + The empty security requirement on this operation does not authorize anonymous + `client_credentials` issuance. It only allows the alternate client-authentication + methods described above (for example mutual TLS). For the baseline + `client_credentials` grant, servers shall require HTTP Basic client authentication + and shall reject unauthenticated token requests with `401`. operationId: requestToken security: - basicAuth: [] @@ -785,6 +895,10 @@ paths: $ref: "#/components/responses/discovery-response" '400': $ref: "#/components/responses/400-invalid-request" + '401': + $ref: "#/components/responses/401-unauthorized" + '403': + $ref: "#/components/responses/403-forbidden" '404': $ref: "#/components/responses/404-object-by-id-not-found" tags: @@ -2023,6 +2137,10 @@ components: otherwise invalid. Servers shall include a `WWW-Authenticate` header as defined in RFC 6750 section 3. On the `invalid_token` error a client may obtain a fresh token from `/token` and retry the request once (RFC 6750 section 3.1). + + Open servers that require no authentication on any endpoint shall not return this + status. On a mixed server, only protected endpoints return `401`; open endpoints + answer without requiring a Bearer token. headers: WWW-Authenticate: description: Bearer challenge, as defined in RFC 6750 section 3. @@ -2031,6 +2149,19 @@ components: example: Bearer realm="tea", error="invalid_token", error_description="The access token expired" content: application/json: {} + 403-forbidden: + description: | + The client is authenticated, but is not authorized to access this resource or + perform this operation. Authorization decisions are server-specific and are not + constrained by this specification. + + Servers may instead conceal the existence of a resource by answering `404` (see + `404-object-by-id-not-found`). Clients shall treat `403` and a concealing `404` as + non-access; neither implies that retrying with the same token will succeed. + content: + application/json: + schema: + $ref: "#/components/schemas/error-response" artifact-content: description: | The content of the requested TEA Artifact format. @@ -2176,7 +2307,11 @@ components: schema: $ref: "#/components/schemas/token-error-response" 404-object-by-id-not-found: - description: Object requested by identifier not found + description: | + Object requested by identifier not found, or the server intentionally conceals + the object's existence from this client (for example when the client is + unauthenticated or authenticated but not authorized). Clients shall not infer + from `404` alone whether the object is absent or withheld. content: application/json: schema: @@ -2458,6 +2593,15 @@ components: A TEA access token obtained from `/token`, presented as `Authorization: Bearer ` (RFC 6750). This is the only credential accepted on TEA endpoints other than `/token`. + + Resource operations declare both this requirement and an empty alternative so + that open and mixed servers are representable: a client may call without a + token. Whether a given endpoint actually requires a token is discovered at + runtime from a `401` Bearer challenge (see `401-unauthorized`), not from this + document alone. Open servers shall ignore a presented Bearer token; mixed + servers may leave some endpoints open and protect others. Authorization after + authentication (filtered views, `403`, or concealing `404`) remains + server-specific. basicAuth: type: http scheme: basic @@ -2467,7 +2611,10 @@ components: 2.3.1). Servers shall not accept API key credentials directly on other TEA endpoints; clients shall exchange them for an access token first. security: + # Bearer token, or anonymous access (open endpoint / open server). Protected + # endpoints still answer 401 with a Bearer challenge when a valid token is required. - bearerAuth: [] + - {} tags: - name: TEA Authentication - name: TEA Product From 3169c8a70fec604c2a771bb1d9bacff1afe3af07 Mon Sep 17 00:00:00 2001 From: Mehrn0ush Date: Fri, 11 Sep 2026 21:08:55 +0800 Subject: [PATCH 2/4] Expand /token alternate client-auth examples beyond mutual TLS. Signed-off-by: Mehrn0ush --- auth/readme.md | 3 ++- spec/openapi.yaml | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/auth/readme.md b/auth/readme.md index 0702c57..7204de9 100644 --- a/auth/readme.md +++ b/auth/readme.md @@ -217,7 +217,8 @@ that a token is required there. The complete flow for a client that does not kno 4. The client calls `POST /token` with its credential - for the baseline, the API key over HTTP Basic - and receives an access token. Unauthenticated `client_credentials` requests are not permitted; an empty OpenAPI security requirement on `/token` allows alternate client - authentication (for example mutual TLS), not anonymous token issuance. + authentication (for example, mutual TLS, `private_key_jwt`, or credentials in the + request body), not anonymous token issuance. 5. The client repeats the resource request with `Authorization: Bearer `, and presents the same token on subsequent requests until it expires or is rejected. 6. When a later request fails with `401` and `error="invalid_token"`, the client obtains a fresh diff --git a/spec/openapi.yaml b/spec/openapi.yaml index aed7cbe..c52d8e3 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -840,9 +840,10 @@ paths: The empty security requirement on this operation does not authorize anonymous `client_credentials` issuance. It only allows the alternate client-authentication - methods described above (for example mutual TLS). For the baseline - `client_credentials` grant, servers shall require HTTP Basic client authentication - and shall reject unauthenticated token requests with `401`. + methods described above (for example, mutual TLS, `private_key_jwt`, or + credentials in the request body). For the baseline `client_credentials` grant, + servers shall require HTTP Basic client authentication and shall reject + unauthenticated token requests with `401`. operationId: requestToken security: - basicAuth: [] From bf5e68a532f4713965bc3b2e6a68ac8483caa07d Mon Sep 17 00:00:00 2001 From: Mehrn0ush Date: Wed, 16 Sep 2026 18:50:37 +0800 Subject: [PATCH 3/4] docs: drop open/mixed server terms from TEA auth wording (#269) Describe servers by whether they require authentication or protect only some objects, and frame the client flow as In TEA rather than OpenAPI. Signed-off-by: Mehrn0ush --- auth/readme.md | 45 +++++++++++++++++++++++---------------------- spec/openapi.yaml | 18 ++++++++++-------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/auth/readme.md b/auth/readme.md index 7204de9..a71f450 100644 --- a/auth/readme.md +++ b/auth/readme.md @@ -28,11 +28,11 @@ authorization - does not make much sense, since the information is usually in th ## Scope of this specification This specification does not require a TEA service to authenticate its users. A service that -publishes openly need not implement any of what follows, including the token endpoint; see -[Servers without authentication](#servers-without-authentication) for what such a server and its -clients do instead. A service that requires authentication for any of its endpoints is a service -that requires authentication, and implements the baseline in full; see -[Mixed servers](#mixed-servers). +requires no authentication need not implement any of what follows, including the token endpoint; +see [Servers without authentication](#servers-without-authentication) for what such a server and +its clients do instead. A service that requires authentication for some or all of its objects +implements the baseline in full; see +[Servers that protect some objects](#servers-that-protect-some-objects). Where a service does authenticate, interoperability requires that every TEA client can authenticate against every TEA server without server-specific code. This specification therefore @@ -203,10 +203,10 @@ certificate. This restates RFC 6749 section 3.2 and RFC 6750 section 5. ## Client flow Whether a server requires authentication is discovered by using it, not by configuration and not by -probing the token endpoint. In the OpenAPI document, resource operations list both Bearer -authentication and an empty security requirement so open and mixed deployments are representable; -a `401` with a `WWW-Authenticate: Bearer` challenge on a given endpoint is still the runtime signal -that a token is required there. The complete flow for a client that does not know in advance: +probing the token endpoint. In TEA, resource operations may be called with a Bearer token or +without one; a `401` with a `WWW-Authenticate: Bearer` challenge on a given endpoint is the +runtime signal that a token is required there. The complete flow for a client that does not know +in advance: 1. The client sends the resource request it wants, with no credentials. 2. If the server does not require authentication for that endpoint, it answers `200` with the @@ -239,31 +239,32 @@ pattern and is preferred over any configuration or discovery step a client would ### Servers without authentication -A server that requires no authentication on any endpoint: +A server that requires no authentication on any endpoint (and therefore applies no authorization): * __need not__ implement the token endpoint. There is nothing to exchange: an OAuth 2.0 token response has to carry an access token, and the mandatory grant requires the client to - authenticate, so a token endpoint on an open server could only issue a token that means nothing. + authenticate, so a token endpoint on such a server could only issue a token that means nothing. * __shall not__ answer any resource request with `401`. Its clients complete step 2 of the flow above and never look for the token endpoint. * __shall__ ignore, rather than reject, an `Authorization: Bearer` header a client presents anyway, for example a client that obtained a token elsewhere or applies one by habit. A token has no - meaning on an open server, and ignoring it keeps such clients working. + meaning on such a server, and ignoring it keeps such clients working. -### Mixed servers +### Servers that protect some objects -A server may publish some endpoints openly and require authentication for others - for example, -listing products and releases openly while restricting artifact downloads to customers, as -described under [Requirements](#requirements). Such a server is a server that requires -authentication: +A TEA server may make some data available without authentication while requiring authentication +and authorization for other objects - for example, listing products and releases without a token +while restricting artifact downloads to customers, as described under +[Requirements](#requirements). Such a server requires authentication for the protected objects: -* it __shall__ implement the token endpoint and the baseline exchange, because at least one endpoint +* it __shall__ implement the token endpoint and the baseline exchange, because at least one object needs them; -* its open endpoints behave as on a server without authentication: they answer without a token and - ignore a token that is presented; -* its protected endpoints answer `401` with the `Bearer` challenge when no valid token is presented, +* endpoints that do not require authentication answer without a token and ignore a token that is + presented; +* protected endpoints answer `401` with the `Bearer` challenge when no valid token is presented, which is how a client learns, per endpoint, that a token is needed. A client __should not__ assume - that a server which served one endpoint openly will serve every endpoint openly, nor the reverse. + that a server which served one object without authentication will serve every object without + authentication, nor the reverse. Authorization - which of the protected data an authenticated client may see - is the server's decision and is not constrained by this specification; a client with a valid token may still receive diff --git a/spec/openapi.yaml b/spec/openapi.yaml index c52d8e3..d15e418 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -2139,8 +2139,9 @@ components: in RFC 6750 section 3. On the `invalid_token` error a client may obtain a fresh token from `/token` and retry the request once (RFC 6750 section 3.1). - Open servers that require no authentication on any endpoint shall not return this - status. On a mixed server, only protected endpoints return `401`; open endpoints + Servers that require no authentication on any endpoint shall not return this + status. On a TEA server where some data is available without authorization, but not + all, protected endpoints return `401`; endpoints that do not require authentication answer without requiring a Bearer token. headers: WWW-Authenticate: @@ -2596,11 +2597,12 @@ components: accepted on TEA endpoints other than `/token`. Resource operations declare both this requirement and an empty alternative so - that open and mixed servers are representable: a client may call without a - token. Whether a given endpoint actually requires a token is discovered at + that a server can select which objects require authentication: a client may call + without a token. Whether a given endpoint actually requires a token is discovered at runtime from a `401` Bearer challenge (see `401-unauthorized`), not from this - document alone. Open servers shall ignore a presented Bearer token; mixed - servers may leave some endpoints open and protect others. Authorization after + document alone. A server that requires no authentication shall ignore a presented + Bearer token. A server that protects some objects but not all may leave some + endpoints without authentication and protect others. Authorization after authentication (filtered views, `403`, or concealing `404`) remains server-specific. basicAuth: @@ -2612,8 +2614,8 @@ components: 2.3.1). Servers shall not accept API key credentials directly on other TEA endpoints; clients shall exchange them for an access token first. security: - # Bearer token, or anonymous access (open endpoint / open server). Protected - # endpoints still answer 401 with a Bearer challenge when a valid token is required. + # Bearer token, or no token (server or endpoint that does not require authentication). + # Protected endpoints still answer 401 with a Bearer challenge when a valid token is required. - bearerAuth: [] - {} tags: From c05fa0348ff14802dfcf56138c41d4fc4be58faf Mon Sep 17 00:00:00 2001 From: Mehrn0ush Date: Wed, 16 Sep 2026 21:40:34 +0800 Subject: [PATCH 4/4] fix: align concealing 404 with 401 client discovery (#269) Limit concealment to authenticated-but-unauthorized clients, keep 401 as the no-token signal, align 403/401 bodies, and tighten /token Basic errors. Signed-off-by: Mehrn0ush --- auth/readme.md | 22 +++++++++++--------- spec/openapi.yaml | 52 +++++++++++++++++++++++++++++------------------ 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/auth/readme.md b/auth/readme.md index a71f450..a221cd8 100644 --- a/auth/readme.md +++ b/auth/readme.md @@ -215,10 +215,10 @@ in advance: the `Bearer` scheme, as described in RFC 6750 section 3. This challenge is the only signal a client needs. 4. The client calls `POST /token` with its credential - for the baseline, the API key over HTTP - Basic - and receives an access token. Unauthenticated `client_credentials` requests are not - permitted; an empty OpenAPI security requirement on `/token` allows alternate client - authentication (for example, mutual TLS, `private_key_jwt`, or credentials in the - request body), not anonymous token issuance. + Basic - and receives an access token. Unauthenticated `client_credentials` requests shall + not be accepted; an empty OpenAPI security requirement on `/token` allows alternate client + authentication (for example, mutual TLS, JWT client assertion (RFC 7523), or credentials in + the request body), not anonymous token issuance. 5. The client repeats the resource request with `Authorization: Bearer `, and presents the same token on subsequent requests until it expires or is rejected. 6. When a later request fails with `401` and `error="invalid_token"`, the client obtains a fresh @@ -261,15 +261,17 @@ while restricting artifact downloads to customers, as described under needs them; * endpoints that do not require authentication answer without a token and ignore a token that is presented; -* protected endpoints answer `401` with the `Bearer` challenge when no valid token is presented, - which is how a client learns, per endpoint, that a token is needed. A client __should not__ assume - that a server which served one object without authentication will serve every object without - authentication, nor the reverse. +* protected endpoints __shall__ answer `401` with the `Bearer` challenge when no valid token is + presented, which is how a client learns, per endpoint, that a token is needed. They shall not + answer a concealing `404` solely because the client is unauthenticated. A client __should not__ + assume that a server which served one object without authentication will serve every object + without authentication, nor the reverse. Authorization - which of the protected data an authenticated client may see - is the server's decision and is not constrained by this specification; a client with a valid token may still receive -a filtered view, `403 Forbidden`, or a concealing `404 Not Found` for individual objects. Clients -shall not infer from `404` alone whether the object is absent or withheld. +a filtered view, `403 Forbidden`, or a concealing `404 Not Found` for individual objects it is +not authorized to see. Clients shall not infer from `404` alone whether the object is absent or +withheld. ### Protected resource metadata diff --git a/spec/openapi.yaml b/spec/openapi.yaml index d15e418..27d5417 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -840,10 +840,11 @@ paths: The empty security requirement on this operation does not authorize anonymous `client_credentials` issuance. It only allows the alternate client-authentication - methods described above (for example, mutual TLS, `private_key_jwt`, or - credentials in the request body). For the baseline `client_credentials` grant, - servers shall require HTTP Basic client authentication and shall reject - unauthenticated token requests with `401`. + methods described above (for example, mutual TLS, JWT client assertion + (RFC 7523), or credentials in the request body). For the baseline + `client_credentials` grant with HTTP Basic, unauthenticated requests shall not be + accepted: servers shall respond with `401`, `error: invalid_client`, and a + `WWW-Authenticate: Basic` challenge (see `401-token-error`; RFC 6749 section 5.2). operationId: requestToken security: - basicAuth: [] @@ -2140,9 +2141,12 @@ components: token from `/token` and retry the request once (RFC 6750 section 3.1). Servers that require no authentication on any endpoint shall not return this - status. On a TEA server where some data is available without authorization, but not - all, protected endpoints return `401`; endpoints that do not require authentication - answer without requiring a Bearer token. + status. On a TEA server where some data is available without authentication, but + not all, protected endpoints return `401` when no valid token is presented; + endpoints that do not require authentication answer without requiring a Bearer + token. A protected object shall not answer `404` solely because the client is + unauthenticated: absence of a valid token yields `401`, so the client flow can + discover that authentication is required. headers: WWW-Authenticate: description: Bearer challenge, as defined in RFC 6750 section 3. @@ -2157,13 +2161,12 @@ components: perform this operation. Authorization decisions are server-specific and are not constrained by this specification. - Servers may instead conceal the existence of a resource by answering `404` (see - `404-object-by-id-not-found`). Clients shall treat `403` and a concealing `404` as - non-access; neither implies that retrying with the same token will succeed. + Servers may instead conceal the existence of a resource from an authenticated + but unauthorized client by answering `404` (see `404-object-by-id-not-found`). + Clients shall treat `403` and that concealing `404` as non-access; neither + implies that retrying with the same token will succeed. content: - application/json: - schema: - $ref: "#/components/schemas/error-response" + application/json: {} artifact-content: description: | The content of the requested TEA Artifact format. @@ -2296,8 +2299,11 @@ components: $ref: "#/components/schemas/token-error-response" 401-token-error: description: | - Client authentication failed. Returned instead of 400 when the client attempted - to authenticate using the `Authorization` header (RFC 6749 section 5.2). + Client authentication failed (`error: invalid_client`). Returned instead of 400 + when the client attempted to authenticate using the `Authorization` header + (RFC 6749 section 5.2). For the baseline Basic exchange this includes a missing + or invalid `Authorization: Basic` credential. Servers shall include + `WWW-Authenticate: Basic`. headers: WWW-Authenticate: description: Challenge indicating the client authentication methods supported by the token endpoint. @@ -2308,12 +2314,17 @@ components: application/json: schema: $ref: "#/components/schemas/token-error-response" + example: + error: invalid_client 404-object-by-id-not-found: description: | Object requested by identifier not found, or the server intentionally conceals - the object's existence from this client (for example when the client is - unauthenticated or authenticated but not authorized). Clients shall not infer - from `404` alone whether the object is absent or withheld. + the object's existence from an authenticated but unauthorized client. Clients + shall not infer from `404` alone whether the object is absent or withheld. + + Concealment applies only after authentication. A request to a protected object + with no valid access token shall receive `401` (see `401-unauthorized`), not a + concealing `404`. content: application/json: schema: @@ -2603,8 +2614,9 @@ components: document alone. A server that requires no authentication shall ignore a presented Bearer token. A server that protects some objects but not all may leave some endpoints without authentication and protect others. Authorization after - authentication (filtered views, `403`, or concealing `404`) remains - server-specific. + authentication (filtered views, `403`, or a concealing `404` for an authenticated + but unauthorized client) remains server-specific. A protected object with no + valid token shall answer `401`, not a concealing `404`. basicAuth: type: http scheme: basic