You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by "nic-6443 (via GitHub)" <gi...@apache.org> on 2026/08/16 14:40:24 UTC

[PR] fix(openid-connect): enforce audience, issuer and required scopes [apisix]

nic-6443 opened a new pull request, #13829:
URL: https://github.com/apache/apisix/pull/13829

   ### Description
   
   Three checks the `openid-connect` config asks for don't run on some paths, so a request the operator meant to reject gets through. They're all in the same plugin, so I put them in one PR.
   
   **`claim_validator.audience.match_with_client_id` is skipped when `aud` is absent.** The comparison is guarded by `audience_value ~= nil`, and `audience.required` defaults to false and isn't implied by `match_with_client_id`, so a validly signed token that simply omits `aud` is accepted while one with the wrong `aud` is rejected. A token without an audience can't match the client id, so it's now treated like a missing required audience.
   
   **Issuer validation is skipped when the discovery document can't be fetched.** With no explicit `valid_issuers`, the trusted issuer comes from discovery; a fetch error only logs a warning and `bearer_jwt_verify` then runs with `opts.valid_issuers` unset, i.e. with no issuer constraint. In `public_key`/`use_jwks` mode the signature check is offline, so while discovery is down a token from any issuer signed with a key APISIX trusts is accepted. Now the token is rejected while no trusted issuer is known — an explicit `claim_validator.issuer.valid_issuers` keeps working without touching discovery.
   
   **`required_scopes` isn't enforced in the authorization code flow.** `required_scopes_present()` is only called inside the `bearer_only or introspection_endpoint or public_key or use_jwks` branch. A route protected by the interactive session flow goes through `openidc.authenticate()` and never reaches it, so every authenticated user passes regardless of their scopes. The check now runs on that path too, reading the granted scopes from the `scope` claim of the access token (a JWT for the providers that support scope-based authorization), falling back to the ID token claims. A session whose granted scopes can't be determined is rejected rather than allowed unchecked — the operator asked for scope-based authorization, and silently skipping it is the bug being fixed here.
   
   Tests: `t/plugin/openid-connect-claim-validation.t` covers the audience and issuer paths standalone (self-signed JWT + `public_key`, no ID provider needed), `t/plugin/openid-connect-required-scopes.t` covers the session flow against Keycloak. Both fail on master and pass with this change.
   
   Behaviour change to note for the release notes: configurations that rely on any of the three gaps above will now see 401/403 where the request used to pass.
   
   #### Which issue(s) this PR fixes:
   
   N/A
   
   ### Checklist
   
   - [x] I have explained the need for this PR and the problem it solves
   - [x] I have explained the changes or the new features added to this PR
   - [x] I have added tests corresponding to this change
   - [x] I have updated the documentation to reflect this change
   - [ ] I have verified that this change is backward compatible (If not, please discuss on the [APISIX mailing list](https://github.com/apache/apisix/tree/master#community) first) — see the behaviour change noted above
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(openid-connect): enforce audience, issuer and required scopes [apisix]

Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #13829:
URL: https://github.com/apache/apisix/pull/13829#discussion_r3792004669


##########
docs/zh/latest/plugins/openid-connect.md:
##########
@@ -46,7 +46,7 @@ import TabItem from '@theme/TabItem';
 | client_secret | string | 是 | | | OAuth 客户端密钥。 |
 | discovery | string | 是 | | | OpenID 提供商的 well-known 发现文档 URL,包含 OP API 端点列表。插件可直接使用发现文档中的端点。你也可以单独配置这些端点,单独配置的值优先于发现文档中提供的端点。 |
 | scope | string | 否 | openid | | 与认证用户相关信息对应的 OIDC 范围,也称为 [claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims)。用于授权具有适当权限的用户。默认值为 `openid`,这是 OIDC 返回唯一标识认证用户的 `sub` claim 所需的范围。可以附加额外的范围并以空格分隔,例如 `openid email profile`。 |
-| required_scopes | array[string] | 否 | | | 访问令牌中必须存在的范围。在 `bearer_only` 为 `true` 时与 introspection 端点结合使用。如果缺少任何必需范围,插件将以 403 forbidden 错误拒绝请求。 |
+| required_scopes | array[string] | 否 | | | 访问令牌中必须存在的范围。如果缺少任何必需范围,插件将以 403 forbidden 错误拒绝请求。在授权码流程中,已授予的范围取自访问令牌的 `scope` 声明,当访问令牌不是 JWT 时则取自 ID 令牌;无法确定已授予范围的会话同样会被拒绝。 |

Review Comment:
   `required_scopes` 的说明写的是“当访问令牌不是 JWT 时才从 ID 令牌取 scope”,但实现是在无法从访问令牌读取 `scope` 时就会回退(包括访问令牌是 JWT 但不含 `scope` 声明的情况)。建议调整文档措辞以与实际行为一致。



##########
docs/en/latest/plugins/openid-connect.md:
##########
@@ -46,7 +46,7 @@ The `openid-connect` Plugin supports the integration with [OpenID Connect (OIDC)
 | client_secret | string | True | | | OAuth client secret. |
 | discovery | string | True | | | URL to the well-known discovery document of the OpenID provider, which contains a list of OP API endpoints. The Plugin can directly utilize the endpoints from the discovery document. You can also configure these endpoints individually, which takes precedence over the endpoints supplied in the discovery document. |
 | scope | string | False | openid | | OIDC scope that corresponds to information that should be returned about the authenticated user, also known as [claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). This is used to authorize users with proper permission. The default value is `openid`, the required scope for OIDC to return a `sub` claim that uniquely identifies the authenticated user. Additional scopes can be appended and delimited by spaces, such as `openid email profile`. |
-| required_scopes | array[string] | False | | | Scopes required to be present in the access token. Used in conjunction with the introspection endpoint when `bearer_only` is `true`. If any required scope is missing, the Plugin rejects the request with a 403 forbidden error. |
+| required_scopes | array[string] | False | | | Scopes required to be present in the access token. If any required scope is missing, the Plugin rejects the request with a 403 forbidden error. In the authorization code flow, the granted scopes are read from the `scope` claim of the access token, or of the ID token when the access token is not a JWT; a session whose granted scopes cannot be determined is rejected as well. |

Review Comment:
   The description of `required_scopes` says the ID token is only used when the access token is not a JWT, but the implementation falls back whenever the access token’s `scope` cannot be read (including the case where it is a JWT but has no `scope` claim). Update the docs wording to match the actual behavior so operators know when a session will be rejected.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(openid-connect): enforce audience, issuer and required scopes [apisix]

Posted by "nic-6443 (via GitHub)" <gi...@apache.org>.
nic-6443 commented on code in PR #13829:
URL: https://github.com/apache/apisix/pull/13829#discussion_r3792151142


##########
docs/zh/latest/plugins/openid-connect.md:
##########
@@ -46,7 +46,7 @@ import TabItem from '@theme/TabItem';
 | client_secret | string | 是 | | | OAuth 客户端密钥。 |
 | discovery | string | 是 | | | OpenID 提供商的 well-known 发现文档 URL,包含 OP API 端点列表。插件可直接使用发现文档中的端点。你也可以单独配置这些端点,单独配置的值优先于发现文档中提供的端点。 |
 | scope | string | 否 | openid | | 与认证用户相关信息对应的 OIDC 范围,也称为 [claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims)。用于授权具有适当权限的用户。默认值为 `openid`,这是 OIDC 返回唯一标识认证用户的 `sub` claim 所需的范围。可以附加额外的范围并以空格分隔,例如 `openid email profile`。 |
-| required_scopes | array[string] | 否 | | | 访问令牌中必须存在的范围。在 `bearer_only` 为 `true` 时与 introspection 端点结合使用。如果缺少任何必需范围,插件将以 403 forbidden 错误拒绝请求。 |
+| required_scopes | array[string] | 否 | | | 访问令牌中必须存在的范围。如果缺少任何必需范围,插件将以 403 forbidden 错误拒绝请求。在授权码流程中,已授予的范围取自访问令牌的 `scope` 声明,当访问令牌不是 JWT 时则取自 ID 令牌;无法确定已授予范围的会话同样会被拒绝。 |

Review Comment:
   Good catch, fixed in 7dc9a28dd — the wording now says the ID token is the fallback whenever the access token carries no `scope` claim.



##########
docs/en/latest/plugins/openid-connect.md:
##########
@@ -46,7 +46,7 @@ The `openid-connect` Plugin supports the integration with [OpenID Connect (OIDC)
 | client_secret | string | True | | | OAuth client secret. |
 | discovery | string | True | | | URL to the well-known discovery document of the OpenID provider, which contains a list of OP API endpoints. The Plugin can directly utilize the endpoints from the discovery document. You can also configure these endpoints individually, which takes precedence over the endpoints supplied in the discovery document. |
 | scope | string | False | openid | | OIDC scope that corresponds to information that should be returned about the authenticated user, also known as [claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). This is used to authorize users with proper permission. The default value is `openid`, the required scope for OIDC to return a `sub` claim that uniquely identifies the authenticated user. Additional scopes can be appended and delimited by spaces, such as `openid email profile`. |
-| required_scopes | array[string] | False | | | Scopes required to be present in the access token. Used in conjunction with the introspection endpoint when `bearer_only` is `true`. If any required scope is missing, the Plugin rejects the request with a 403 forbidden error. |
+| required_scopes | array[string] | False | | | Scopes required to be present in the access token. If any required scope is missing, the Plugin rejects the request with a 403 forbidden error. In the authorization code flow, the granted scopes are read from the `scope` claim of the access token, or of the ID token when the access token is not a JWT; a session whose granted scopes cannot be determined is rejected as well. |

Review Comment:
   Fixed in 7dc9a28dd: the ID token is described as the fallback whenever the access token carries no `scope` claim, not only when it isn't a JWT.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(openid-connect): enforce audience, issuer and required scopes [apisix]

Posted by "nic-6443 (via GitHub)" <gi...@apache.org>.
nic-6443 merged PR #13829:
URL: https://github.com/apache/apisix/pull/13829


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org