Skip to content

feat(oauth2): populate groups claim in client_credentials tokens#4691

Open
carlesarnal wants to merge 2 commits intodexidp:masterfrom
carlesarnal:fix/client-credentials-groups
Open

feat(oauth2): populate groups claim in client_credentials tokens#4691
carlesarnal wants to merge 2 commits intodexidp:masterfrom
carlesarnal:fix/client-credentials-groups

Conversation

@carlesarnal
Copy link
Copy Markdown

@carlesarnal carlesarnal commented Mar 26, 2026

Overview

Add a ClientCredentialsClaims sub-struct to the Client definition, allowing static clients to declare identity claims (starting with groups) that are included in tokens issued via client_credentials.

What this PR does / why we need it

PR #4583 added client_credentials grant support and lists groups as a supported scope. However, the scope is only accepted (not rejected) — the Groups field in the claims struct is never populated, so tokens never contain a groups claim regardless of the scope requested.

This is because client_credentials has no upstream connector or user involved, so there is no source for group membership. This PR adds a clientCredentialsClaims sub-struct to static client definitions, keeping identity attributes separate from core Client fields (ID, secret, redirect URIs).

Design decision: Per reviewer feedback, identity attributes are scoped to a dedicated ClientCredentialsClaims struct rather than added as top-level fields on Client. This avoids mixing application identity with user-like identity attributes and provides a clean extension point for future claims (email, custom attributes) without each addition leaking into the client definition.

Changes:

  1. storage/storage.go — Added ClientCredentialsClaims struct with Groups field, and an optional ClientCredentialsClaims pointer on Client
  2. server/handlers.go — In handleClientCredentialsGrant, populate claims.Groups from client.ClientCredentialsClaims.Groups when the groups scope is requested
  3. server/handlers_test.go — Added two test cases:
    • Client with clientCredentialsClaims.groups + groups scope → groups appear in token
    • Client without clientCredentialsClaims + groups scope → no groups in token (no regression)

Configuration example:

staticClients:
  - id: my-service
    name: My Service
    secret: "..."
    clientCredentialsClaims:
      groups:
        - admin-group
        - dev-group

Why this matters:

Applications that use the groups claim for RBAC — such as Apicurio Registry — cannot authorize client_credentials tokens for write operations without groups. The Apicurio Kafka SerDes libraries only support client_credentials (they hardcode grant_type=client_credentials), so this is a blocking issue for Kafka schema registry use cases.

Testing:

  • go test ./server/... -run TestHandleClientCredentials — all 10 cases pass (8 existing + 2 new)
  • Manually tested on an OpenShift cluster with a patched Dex image: client_credentials tokens now include groups and Apicurio Registry successfully resolves RBAC roles — write operations return 200 instead of 403

Fixes #4690

@carlesarnal carlesarnal force-pushed the fix/client-credentials-groups branch from 01d6288 to ac08ebb Compare March 26, 2026 19:03
carlesarnal added a commit to carlesarnal/apicurio.github.io that referenced this pull request Mar 26, 2026
- Groups scope verified working on live cluster (v2.45.1)
- client_credentials: works on dex:master, not in v2.45.1
- groups claim missing from client_credentials tokens (accepted but
  not populated)
- Link to fix: dexidp/dex#4691 (adds groups field to static clients)
- Verified fix on cluster: writes succeed with patched Dex
- Added unsupported_grant_type troubleshooting entry
@carlesarnal carlesarnal marked this pull request as ready for review March 27, 2026 07:08
@carlesarnal carlesarnal force-pushed the fix/client-credentials-groups branch from ac08ebb to 44f15e7 Compare March 27, 2026 11:32
Copy link
Copy Markdown
Member

@nabokihms nabokihms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks good. The only thing required is the storage code.

Add a ClientCredentialsClaims sub-struct to the Client definition,
allowing static clients to declare identity claims (starting with
groups) that are included in tokens issued via client_credentials.

This keeps the Client struct focused on application-level concerns
(ID, secret, redirect URIs) while providing a dedicated home for
identity attributes needed by RBAC-aware consumers.

Configuration example:

    staticClients:
      - id: my-service
        secret: "..."
        clientCredentialsClaims:
          groups:
            - admin-group

When the groups scope is requested in a client_credentials grant,
the groups from clientCredentialsClaims are included in the token.
If clientCredentialsClaims is not set, behavior is unchanged.

Fixes dexidp#4690

Signed-off-by: Carles Arnal <carlesarnal92@gmail.com>
Add the client_credentials_claims field to the ent ORM schema and
update the storage client to persist and retrieve it. Also fix gci
import formatting in handlers_test.go.

Signed-off-by: Carles Arnal <carnalca@redhat.com>
Signed-off-by: Carles Arnal <carlesarnal92@gmail.com>
@carlesarnal carlesarnal force-pushed the fix/client-credentials-groups branch from 5ffd90c to 83b7b4a Compare April 7, 2026 10:37
carlesarnal added a commit to Apicurio/apicurio.github.io that referenced this pull request Apr 9, 2026
* Add blog post: Universal OAuth integration via Dex

Covers evolving the Apicurio Registry authentication story from the
OpenShift-specific direct integration to a universal approach using Dex
as an OIDC bridge, enabling any OAuth provider (OpenShift, LDAP, GitHub,
Azure AD, SAML) with full feature support including UI login, principal
identity, and owner-based authorization.

* Fix blog post: correct Dex/Registry client architecture and config

Fixes 10 issues including: two-client architecture (public UI + confidential
backend), CORS allowedOrigins, correct appClientId/uiClientId, UI auth
scope for groups, redirectUri, ingress.host with edge TLS annotations,
REGISTRY_API_URL override, /dashboard redirect URI, and expanded
troubleshooting section.

* Refine blog post: improve inline explanations and comments

Moves key explanations (two-client architecture, UI scope, redirectUri)
into inline YAML comments and a dedicated callout section after the CR,
removes Group-based access row from comparison table, adds --port flag
to oc route command, and cleans up redundant prose.

* Add M2M access section to Dex blog post

Covers machine-to-machine authentication patterns: authorization code
flow with the confidential client for scripts, Dex's lack of
client_credentials grant, refresh token approach for CI/CD pipelines,
and the option to bypass Dex for native M2M providers.

* Remove DEX-OAUTH-INTEGRATION.md from branch, update comparison table

The guide file should not be part of this PR. Also adds M2M row to the
feature comparison table noting Dex does not support client credentials.

* Update blog: add Dex client_credentials test results and fix PR

- Groups scope verified working on live cluster (v2.45.1)
- client_credentials: works on dex:master, not in v2.45.1
- groups claim missing from client_credentials tokens (accepted but
  not populated)
- Link to fix: dexidp/dex#4691 (adds groups field to static clients)
- Verified fix on cluster: writes succeed with patched Dex
- Added unsupported_grant_type troubleshooting entry

* Update blog: use clientCredentialsClaims sub-struct per reviewer feedback
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

client_credentials grant: groups scope accepted but never populated in token claims

2 participants