Skip to content

Token Exchange Delegation for Clients - #51306

Open
mabartos wants to merge 1 commit into
keycloak:mainfrom
mabartos:KC-51305
Open

Token Exchange Delegation for Clients#51306
mabartos wants to merge 1 commit into
keycloak:mainfrom
mabartos:KC-51305

Conversation

@mabartos

@mabartos mabartos commented Jul 30, 2026

Copy link
Copy Markdown
Member

Pros/Cons of the previous and current approach

delegation:service-account-<client> (old)

Pros

  • Reuses the existing delegation:<username> scope infrastructure — no new scope type, mapper, or validation logic needed
  • Works today without any Keycloak code changes (but in very limited, unsecure and with bad UX way)

Cons

  • Clients must know the service-account-* username pattern
  • Requires granting the impersonation role to the service account, which opens the Admin REST API impersonation endpoint
  • Credential leak = full identity substitution for any user the FGAP policy allows (or the wide impersonation role is assigned), with no audit trail distinguishing it from a real login
  • FGAP must be configured to scope down who can be impersonated — complex setup for what should be a simple app-level delegation
  • Conflates admin impersonation (designed for human operators) with application-level delegation
  • Consent text is generic - same as user-to-user delegation, doesn't mention the client ("Allow this admin...")
  • may_act only contains sub, no explicit client binding for better actor identification
  • Requires a static audience mapper on the subject-app per actor client - manual setup that must be repeated for every new delegation target

delegation:client:<client> (new)

Pros

  • Uses the public client_id directly - no internal naming conventions exposed
  • No admin roles or FGAP needed - authorization is a simple client.delegation.enabled toggle + user consent
  • No Admin API access granted - minimal blast radius on credential leak
  • may_act contains both sub and client_id for better traceability
  • Dedicated consent text: "Allow {client} to act on your behalf?"
  • Clean separation from admin user delegation - distinct scope type, distinct flow
  • Scope auto-registered as realm default - zero manual scope assignment for new clients
  • Simpler demo flow (single click, subject-app sends token to actor-app directly)
  • Audience is resolved dynamically from the scope parameter - no per-client audience mapper configuration needed

Cons

  • New scope type, mapper, and validation logic - more code to maintain

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unreported flaky test detected, please review

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.adapter.servlet.SAMLServletAdapterTest#employeeSigPostNoIdpKeyTestCertSubjectAsKeyNameInKeyInfo

Keycloak CI - Adapter IT Strict Cookies

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

@mabartos
mabartos marked this pull request as ready for review July 30, 2026 11:28
@mabartos
mabartos requested a review from a team as a code owner July 30, 2026 11:28
Copilot AI review requested due to automatic review settings July 30, 2026 11:28
@mabartos
mabartos requested a review from a team as a code owner July 30, 2026 11:28
@mabartos
mabartos marked this pull request as draft July 30, 2026 11:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds consent-based client delegation to token exchange, producing scoped tokens with may_act and act actor tracing.

Changes:

  • Adds client delegation scopes, validation, protocol mappers, and configuration.
  • Adds Admin UI capability controls and consent messages.
  • Adds integration coverage for delegation and revocation scenarios.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
themes/.../login/messages_en.properties Adds login consent text.
themes/.../account/messages_en.properties Adds account consent text.
tests/.../ClientDelegationTest.java Tests client delegation flows.
services/.../org.keycloak.protocol.ProtocolMapper Registers the client mapper.
services/.../ParameterizedScopeTypeProvider Registers the scope provider.
services/.../TokenExchangeDelegationProvider.java Validates delegated client identity.
services/.../ClientDelegationScopeType.java Defines client scope validation.
services/.../OIDCLoginProtocolFactory.java Creates delegation scope and mappers.
services/.../ParameterizedScopeUserPropertyMapper.java Makes user resolution extensible.
services/.../ParameterizedScopeClientUserPropertyMapper.java Maps service-account properties.
server-spi-private/.../OIDCConfigAttributes.java Adds the delegation attribute.
js/.../useIsFeatureEnabled.ts Exposes the delegation feature.
js/.../CapabilityConfig.tsx Adds the Admin UI toggle.
js/.../messages_en.properties Adds Admin UI labels and help.

@mabartos
mabartos force-pushed the KC-51305 branch 2 times, most recently from 48511ab to c2dbecc Compare July 31, 2026 13:25
@mabartos

Copy link
Copy Markdown
Member Author

Slightly updated the demo app:

Screencast.From.2026-07-31.15-21-48.mp4

mabartos/token-exchange-delegation-demo@a49ddf4

@mabartos
mabartos marked this pull request as ready for review August 3, 2026 07:56
Copilot AI review requested due to automatic review settings August 3, 2026 07:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

Suppressed comments (2)

tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientDelegationTest.java:446

  • The success path authenticates the exchange as test-app and this static mapper makes the actor token valid for that client, so the tests still pass if the new dynamic audience mapper never adds agent-app to the subject token. Cover the advertised mapper-free flow by enabling standard exchange on agent-app, authenticating the exchange as agent-app, removing this mapper, and asserting the subject token audience.
            ProtocolMapperRepresentation audienceMapper = new ProtocolMapperRepresentation();

services/src/main/java/org/keycloak/protocol/oidc/mappers/ParameterizedScopeClientSubMapper.java:22

  • This mapper is registered as an Admin UI-selectable protocol mapper, but its configuration exposes no token-claim name or JSON type. A mapper created through the UI therefore reaches mapClaim without claim.name and silently emits nothing; expose the standard attribute configuration as the base parameterized mapper does.
        OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, ParameterizedScopeClientSubMapper.class);

Copilot AI review requested due to automatic review settings August 3, 2026 08:12
@mabartos
mabartos marked this pull request as draft August 3, 2026 08:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

themes/src/main/resources/theme/base/login/messages/messages_en.properties:128

  • The base login template calls advancedMsg without the parameter and appends the parameter separately (themes/.../base/login/login-oauth-grant.ftl:24), so this renders literally as Allow {0} to act on your behalf?: agent-app. Pass the parameter to advancedMsg as the keycloak.v2 template does.
clientDelegationScopeConsentText=Allow {0} to act on your behalf?

js/apps/admin-ui/src/clients/add/CapabilityConfig.tsx:505

  • Enabling this switch leaves serviceAccountsEnabled false by default, while ClientDelegationScopeType rejects targets without service accounts; the UI can therefore show delegation as enabled although every request silently drops the scope. Mirror the authorization switch behavior by enabling the service account when delegation is turned on.
              <DefaultSwitchControl
                name={convertAttributeNameToForm<FormFields>(
                  "attributes.client.delegation.enabled",
                )}
                label={t("clientDelegationEnabled")}
                labelIcon={t("clientDelegationEnabledHelp")}
                stringify
              />

Closes keycloak#51305

Signed-off-by: Martin Bartoš <mabartos@redhat.com>
@mabartos
mabartos marked this pull request as ready for review August 3, 2026 09:58
Copilot AI review requested due to automatic review settings August 3, 2026 09:58
@mabartos
mabartos marked this pull request as draft August 3, 2026 09:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java:415

  • This only creates delegation:client while a realm is being created/imported; enabling the feature for an existing realm never runs this path, so the advertised realm-default scope is absent and both existing and subsequently created clients cannot request it without manual setup. Add an existing-realm bootstrap/migration that creates the scope and registers it as an optional default.
            ClientScopeModel clientDelegationScope = newRealm.addClientScope(CLIENT_DELEGATION_SCOPE);

Comment on lines +51 to +59
ClientModel client = userSession.getRealm().getClientByClientId(parameterValue);
if (client == null) {
continue;
}
UserModel serviceAccount = keycloakSession.users().getServiceAccount(client);
if (serviceAccount == null) {
continue;
}
resolvedValues.add(serviceAccount.getId());

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's related also to the "delegation:" user-to-user delegation.

Created a follow-up issue: #51398

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unreported flaky test detected, please review

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.broker.KcOidcBrokerTest#loginWithExistingUserWithBruteForceEnabled

Keycloak CI - Java Distribution IT (windows-latest - temurin - 21)

org.openqa.selenium.TimeoutException: 
Expected condition failed: waiting for value to contain (ignoring case) "sign in to". Current value: "AUTH_RESPONSE" (tried for 5 second(s) with 500 milliseconds interval)
Build info: version: '4.28.1', revision: '73f5ad48a2'
System info: os.name: 'Windows Server 2025', os.arch: 'amd64', os.version: '10.0', java.version: '21.0.11'
Driver info: org.jboss.arquillian.drone.webdriver.htmlunit.DroneHtmlUnitDriver_ByGraphene
...

Report flaky test

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.

Token Exchange Delegation for Clients

2 participants