Description
The openid-connect plugin enforces the claim_schema option only in the session/callback authentication path, not in the bearer-token path.
claim_schema is validated by validate_claims_in_oidcauth_response() in apisix/plugins/openid-connect.lua, and that function has a single call site, inside the openidc.authenticate() branch (session/browser flow). The bearer branch that handles bearer_only / introspection_endpoint / public_key / use_jwks never calls it.
As a result, when a route accepts both authentication modes and a claim_schema is configured:
- requests authenticated via the session/callback flow are checked against
claim_schema
- requests authenticated via a bearer token are not checked against
claim_schema
Why it happens
claim_schema was added together with the OIDC claim validator (#11824) and was wired into the session-flow response, which is the structured object { user, access_token, id_token } returned by openidc.authenticate(). The bearer/introspection/JWKS path returns a flat introspection or JWT payload with a different shape (no id_token, no user wrapper), so it was never routed through the same validation function.
This is plausibly intentional scoping rather than an oversight: the function is named for the openidc.authenticate() response, and claim_schema validates a { user, access_token, id_token } object that only exists in the session flow. The bearer path also already has its own claim controls (claim_validator, required_scopes).
Expected behavior
The behavior of claim_schema across the two authentication paths should be consistent and clearly documented. Either:
- document explicitly that
claim_schema applies only to the session/callback flow, or
- apply an equivalent claim validation step to the bearer/introspection/JWKS path.
A docs-only fix (option 1) is an acceptable resolution for this issue. The current docs describe claim_schema as "JSON schema of OIDC response claim" with no mention of which authentication flow it applies to, which is what invites the wrong assumption. Clarifying that it covers the session/callback flow only is sufficient to close this.
If option 2 is chosen instead, note that the bearer-path response has a different structure than the session-flow response, so the validation object for bearer mode would need to be built accordingly (and the per-flow key differences documented).
Steps to reproduce
- Configure a route with the
openid-connect plugin, bearer_only: false, an introspection_endpoint, and a claim_schema.
- Access the route without credentials and complete the browser login flow: the response is checked against
claim_schema.
- Access the same route with an
Authorization: Bearer <token> header: the response is not checked against claim_schema.
Environment
Description
The
openid-connectplugin enforces theclaim_schemaoption only in the session/callback authentication path, not in the bearer-token path.claim_schemais validated byvalidate_claims_in_oidcauth_response()inapisix/plugins/openid-connect.lua, and that function has a single call site, inside theopenidc.authenticate()branch (session/browser flow). The bearer branch that handlesbearer_only/introspection_endpoint/public_key/use_jwksnever calls it.As a result, when a route accepts both authentication modes and a
claim_schemais configured:claim_schemaclaim_schemaWhy it happens
claim_schemawas added together with the OIDC claim validator (#11824) and was wired into the session-flow response, which is the structured object{ user, access_token, id_token }returned byopenidc.authenticate(). The bearer/introspection/JWKS path returns a flat introspection or JWT payload with a different shape (noid_token, nouserwrapper), so it was never routed through the same validation function.This is plausibly intentional scoping rather than an oversight: the function is named for the
openidc.authenticate()response, andclaim_schemavalidates a{ user, access_token, id_token }object that only exists in the session flow. The bearer path also already has its own claim controls (claim_validator,required_scopes).Expected behavior
The behavior of
claim_schemaacross the two authentication paths should be consistent and clearly documented. Either:claim_schemaapplies only to the session/callback flow, orA docs-only fix (option 1) is an acceptable resolution for this issue. The current docs describe
claim_schemaas "JSON schema of OIDC response claim" with no mention of which authentication flow it applies to, which is what invites the wrong assumption. Clarifying that it covers the session/callback flow only is sufficient to close this.If option 2 is chosen instead, note that the bearer-path response has a different structure than the session-flow response, so the validation object for bearer mode would need to be built accordingly (and the per-flow key differences documented).
Steps to reproduce
openid-connectplugin,bearer_only: false, anintrospection_endpoint, and aclaim_schema.claim_schema.Authorization: Bearer <token>header: the response is not checked againstclaim_schema.Environment