Skip to content

THREESCALE-12102: Add token_exchange_enabled OIDC flow toggle#4313

Open
madnialihussain wants to merge 1 commit into
masterfrom
THREESCALE-12102-support-token-exchange
Open

THREESCALE-12102: Add token_exchange_enabled OIDC flow toggle#4313
madnialihussain wants to merge 1 commit into
masterfrom
THREESCALE-12102-support-token-exchange

Conversation

@madnialihussain

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

Adds token_exchange_enabled as a new OIDC flow toggle. This allows admins to enable/disable Standard Token Exchange (V2) on Keycloak clients via the 3scale UI and API, instead of configuring Keycloak manually.

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): This PR (Porta) + companion Zync PR. Adds the UI/API toggle in Porta that flows through Zync to set standard.token.exchange.enabled on the Keycloak client. Verified end-to-end: toggle ON → Keycloak shows true, toggle OFF → shows false.

  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. Navigate to a service's Integration > Settings > OpenID Connect > OIDC Authorization Flow
  2. Verify a "Token Exchange Flow" checkbox appears
  3. Toggle it on/off and save
  4. Verify via API: GET /admin/api/services/{id}/proxy/oidc_configuration.json returns token_exchange_enabled: true/false
  5. Run tests: bundle exec rails test test/models/oidc_configuration_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.

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

It looks good to me! I modified the Jira ticket to add Apicast and Zync as components. Apicast accepts the JWT which includes user data but does nothing with it AFAIK, It just ignores the user data . I'm not sure what should apicast do with the user token, maybe just deliver it to the backend (maybe already does?), maybe extract it and add some extra header in the request to backend... IDK. But probably from porta side this the only thing we need.

@madnialihussain how did you figure out this was the appropriate thing to do? did anybody help you?

@madnialihussain

Copy link
Copy Markdown
Contributor Author

It looks good to me! I modified the Jira ticket to add Apicast and Zync as components. Apicast accepts the JWT which includes user data but does nothing with it AFAIK, It just ignores the user data . I'm not sure what should apicast do with the user token, maybe just deliver it to the backend (maybe already does?), maybe extract it and add some extra header in the request to backend... IDK. But probably from porta side this the only thing we need.

@madnialihussain how did you figure out this was the appropriate thing to do? did anybody help you?

Thanks @jlledom for the review and for adding the components. No, APIcast doesn't send user data to Apisonator. I verified this in the APIcast logs, the Apisonator call only contains service_id, service_token, usage[hits], and app_id=test-client. No user_id or any user data.

The JWT does get forwarded to the upstream service in the Authorization: Bearer header. I verified this using an echo server, which showed the full JWT in the request headers but no separate user identity headers like X-User-Id.

I went through the ticket, looked at how APIcast handles JWTs and how the existing OIDC toggles work with Zync and Keycloak ( took help with IBM Bob and Claude) then set up a local environment (Keycloak + APIcast + echo server) to test things out. The three requirement conclusions in the PR description are based on that.

@jlledom

jlledom commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Thanks @jlledom for the review and for adding the components. No, APIcast doesn't send user data to Apisonator. I verified this in the APIcast logs, the Apisonator call only contains service_id, service_token, usage[hits], and app_id=test-client. No user_id or any user data.

I'm not sure, but maybe some additional work in required in Apicast, @tkan145 WDYT?

I went through the ticket, looked at how APIcast handles JWTs and how the existing OIDC toggles work with Zync and Keycloak ( took help with IBM Bob and Claude) then set up a local environment (Keycloak + APIcast + echo server) to test things out. The three requirement conclusions in the PR description are based on that.

This PR was not an easy one. Good job!

@tkan145

tkan145 commented Jun 23, 2026

Copy link
Copy Markdown

If I understand the JIRA correctly, the token exchange needs to take place at the gateway.

sequenceDiagram
    participant client
    participant APIcast
    client->>APIcast: <jwt_token>
    APIcast->>APIcast: validate jwt token
    APIcast->>IDP: exchange token
    IDP->>APIcast: new token
    APIcast->>Upstream: calling upstream with new token
Loading

This is a major undertaking, and given 3scale's current lifecycle, I don't plan on making any major refactoring of the gateway.

@jlledom

jlledom commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

If I understand the JIRA correctly, the token exchange needs to take place at the gateway.

I'm not sure the Jira says it's Apicast who needs to exchange the token. But anyway, with the current changes in porta and zync, we already have the machanism for the clients to exchange the token themselves and send it to apicast, which will forward it to upstream and that will work.

The exact changes in apicast are not clearly defined in the issue, or I didn't understood them. IMO we could do anything, we can make apicast exhange the token, or we can add a policy to get the exchanged token and provide the credentials in parameters or headers to upstream. Or do nothing and let upstream get the token.

@akostadinov

Copy link
Copy Markdown
Contributor

Yeah, it reads "Add ... OIDC flow toggle" so theoretically we can add the toggle and skip underlying logic 👼

@akostadinov

Copy link
Copy Markdown
Contributor

Looking at the issue more thoroughly, current approach makes sense to me. To recap Madni's description:

  • porta allows the token type
  • zync enables the token exchange in keycloak
  • apicast already would accept such token and additional validation can happen with existing policies

@tkan145, if I understand your comment correctly, you read the rfe as having APIcast to exchange the token and then use the user identity for rate limiting, stats, etc., is this correct? This also makes sense.

I think we need to go back to the requestor and understand what are they trying to achieve exactly instead of guessing. @madnialihussain , would be good if you can ask in JIRA for more thorough explanation.

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.

4 participants