Skip to content

THREESCALE-12102-support-token-exchange#594

Open
madnialihussain wants to merge 3 commits into
masterfrom
THREESCALE-12102-support-token-exchange
Open

THREESCALE-12102-support-token-exchange#594
madnialihussain wants to merge 3 commits into
masterfrom
THREESCALE-12102-support-token-exchange

Conversation

@madnialihussain

Copy link
Copy Markdown

What this PR does / why we need it:

Maps the token_exchange_enabled OIDC flow from Porta to the Keycloak client attribute standard.token.exchange.enabled, enabling RHBK Standard Token Exchange (V2) on synced clients.

Unlike the other OIDC flows (standardFlowEnabled, implicitFlowEnabled, etc.) which are top-level Keycloak client fields, token exchange is controlled via the client attributes hash with key standard.token.exchange.enabled. This PR handles that difference in KeycloakAdapter::OAuthConfiguration.

Also adds the urn:ietf:params:oauth:grant-type:token-exchange grant type mapping in RESTAdapter::GrantTypes for non-Keycloak OIDC providers that follow RFC 8693.

Ticket requirements (THREESCALE-12102):

  1. Add native support in APIcast for validating OBO tokens: (Verified). APIcast validates OBO tokens out of the box since they are standard JWTs. Tested by obtaining an OBO token via Keycloak token exchange and
    sending it through APIcast, returned HTTP 200.

  2. Ensure compatibility with RHBK Standard Token Exchange (V2): Companion Porta PR adds the UI/API toggle. This Zync PR maps it to Keycloak's standard.token.exchange.enabled attribute. Verified end-to-end: toggle
    ON in Porta → Keycloak client shows standard.token.exchange.enabled: "true", toggle OFF → shows "false", toggle ON again → shows "true".

  3. Provide configuration options to enforce policies based on both client and user claims: (Verified). The OBO token contains both client claims (azp, client_id) and user claims (sub, preferred_username, email,
    roles). APIcast extracts the client identity via jwt_claim_with_client_id (mapped to azp) for rate limiting, and existing policies like keycloak_role_check can enforce rules on user roles. Tested with an unknown
    client azp → APIcast returned HTTP 403.

Which issue(s) this PR fixes

https://redhat.atlassian.net/browse/THREESCALE-12102

Verification steps

  1. Enable token_exchange_enabled on a service in Porta
  2. Verify the Keycloak client has standard.token.exchange.enabled: "true" in its attributes
  3. Verify the 3scale: true attribute is preserved alongside token exchange
  4. Disable token_exchange_enabled and verify the attribute is set to "false"
  5. Run tests: bundle exec rails test test/adapters/keycloak_adapter_test.rb test/adapters/rest_adapter_test.rb

Note: End-to-end testing (Porta → Zync → Keycloak → APIcast) was done by temporarily cherry-picking commits from PR #4310 (OIDC sync token rotation). Will re-test after #4310 is merged.

@madnialihussain madnialihussain changed the title Map token_exchange_enabled to Keycloak standard.token.exchange.enable… THREESCALE-12102-support-token-exchange Jun 8, 2026
jlledom
jlledom previously approved these changes Jun 8, 2026

@jlledom jlledom 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.

I tested thoroughly, against keycloak but also against a rest OIDC server (Hydra). End-to-end sync, from porta to OIDC server works fine for me.

It looks good to me. My only suggestion is to maybe add tests for the rest adapter, to ensure the grant types are correctly sent to the server.

@jlledom

jlledom commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

@madnialihussain End to end can be done without cherry picking, just set the same value for the token directly in the table in both DBs: zync and porta.

On the other hand, did you try end-to-end but using a rest OIDC server instead of keycloak? I used Hydra but that one doesn't support users AFAIK.

@madnialihussain

madnialihussain commented Jun 9, 2026

Copy link
Copy Markdown
Author

It looks good to me. My only suggestion is to maybe add tests for the rest adapter, to ensure the grant types are correctly sent to the server.

Thanks @jlledom! I've added REST adapter tests for the token exchange grant type mapping

@madnialihussain

Copy link
Copy Markdown
Author

@madnialihussain End to end can be done without cherry picking, just set the same value for the token directly in the table in both DBs: zync and porta.

On the other hand, did you try end-to-end but using a rest OIDC server instead of keycloak? I used Hydra but that one doesn't support users AFAIK.

Thanks for the tip on setting the token directly in both DB, Regarding the REST OIDC server with user support, no, I haven't tested with one. I couldn't find an open-source OIDC server that supports users, token exchange, and the generic REST client registration format that our REST adapter expects

Comment thread test/adapters/keycloak_adapter_test.rb Outdated
Comment on lines +117 to +127
test 'oauth flows with token exchange enabled' do
client = KeycloakAdapter::Client.new({
id: 'client_id',
oidc_configuration: {
token_exchange_enabled: true,
}
})
hash = client.to_h
assert_equal 'true', hash[:attributes]['standard.token.exchange.enabled']
assert_equal true, hash[:attributes]['3scale']
end

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.

I don't think we need this test. We have test 'oauth flows' above, which we could just add token_exchange_enabled to it.

We could also modify that test to check for the 3scale attribute.

On the other hand, I think it would be good to have a test that stubs the request, like other tests in the suite do, calls create_client and verifies the proper token exchange attribute is sent to the server.

Comment on lines +49 to +73
test 'oauth flows with token exchange' do
client = RESTAdapter::Client.new(
id: 'foo',
oidc_configuration: {
standard_flow_enabled: true,
token_exchange_enabled: true,
}
)
grant_types = JSON.parse(client.to_json).fetch('grant_types')
assert_includes grant_types, 'authorization_code'
assert_includes grant_types, 'urn:ietf:params:oauth:grant-type:token-exchange'
end

test 'oauth flows without token exchange' do
client = RESTAdapter::Client.new(
id: 'foo',
oidc_configuration: {
standard_flow_enabled: true,
token_exchange_enabled: false,
}
)
grant_types = JSON.parse(client.to_json).fetch('grant_types')
assert_includes grant_types, 'authorization_code'
refute_includes grant_types, 'urn:ietf:params:oauth:grant-type:token-exchange'
end

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.

Same with this tests. They are fine but I think we need to stub the requests and ensure the proper parameter is sent to the server.

@madnialihussain madnialihussain Jun 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the review @jlledom ! I've addressed both comments in f91676f

Also added a test for the token_exchange_enabled: false case to verify it sends "false" to Keycloak

@madnialihussain madnialihussain requested a review from jlledom June 10, 2026 14:00
@madnialihussain madnialihussain force-pushed the THREESCALE-12102-support-token-exchange branch from f91676f to 4938abb Compare June 10, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants