[Bug]: dot-separated --oidc-groups-claim paths are ignored for tokens from extra JWT issuers
OAuth2-Proxy Version
v7.15.4, also present on master (checked at 3847c799)
Provider
oidc
Expected Behaviour
--oidc-groups-claim takes either a plain claim name or a dot separated path. The main OIDC path resolves it that way through getClaimFrom in pkg/providers/util/claim_extractor.go, which tries the value as a key first and then as a JSON path.
A token verified through --extra-jwt-issuers should resolve the same option the same way, so --oidc-groups-claim=realm_access.roles should pick up groups from a nested claim on that path too.
Current Behaviour
The extra JWT issuer path only does a top level key lookup. extractGroups in pkg/apis/middleware/session.go reads the claim out of a map[string]json.RawMessage, so a name containing a dot never matches.
Given --oidc-groups-claim=realm_access.roles and a token carrying:
{"sub": "123", "realm_access": {"roles": ["foo", "bar"]}}the primary issuer resolves groups to ["foo", "bar"] and an extra issuer resolves none.
Nothing is logged and no error is returned, because the key is just absent from the map, and an absent groups claim is treated as a user who belongs to no groups. The request then fails --allowed-group and returns a generic 403.
That is the same silent failure described in #3539. #3539 was about the claim name being hardcoded and #3540 fixed that part. Nested claim names are still affected.
Steps To Reproduce
- Configure the oidc provider with
--oidc-groups-claim=realm_access.roles. - Enable bearer token auth with
--skip-jwt-bearer-tokensand add--extra-jwt-issuers=https://issuer.example.com=my-audience. - Set
--allowed-group=foo. - Mint a token from the extra issuer carrying
realm_access: {"roles": ["foo"]}. - Send it as
Authorization: Bearer <token>. - The request is rejected with a 403 and nothing in the logs points at the groups claim. The same token from the primary issuer is accepted.
Possible Solutions
Resolve the claim with the same extractor the main path uses, so util.NewClaimExtractor(ctx, token, nil, nil) followed by GetClaim(groupsClaim). pkg/providers/util does not import pkg/apis/middleware, so there is no import cycle, and a nil profile URL is handled.
There is a decision to make before doing that. The two paths disagree on what to do with a badly shaped claim. After #3540, extractGroups returns an error when the claim is present but is neither a string nor an array of strings. CoerceClaim on the main path is lenient instead. It turns ["foo", 1] into ["foo", "1"], and it turns an object into a single group whose name is the JSON text of that object.
Reusing GetClaimInto would quietly adopt the lenient behaviour on the extra issuer path. Keeping the stricter check would mean calling GetClaim and type switching the raw value, because a check layered on top of CoerceClaim would never fire.
This is authorization input, so it seems worth deciding on purpose rather than inheriting whichever behaviour comes for free.
Configuration details or additional information
Split out of #3540 at a reviewer's request, so the gap stays visible after that PR merges. Related to #3539.
Relevant code:
pkg/apis/middleware/session.go,extractGroupspkg/providers/util/claim_extractor.go,getClaimFrompkg/util/util.go,CoerceClaimandtoStringSlice
Source: oauth2-proxy/oauth2-proxy