Keycloak 14 OAuth 2.0 Token Exchange: Complete Configuration Guide for Integrating External WSO2 Tokens

This article focuses on OAuth 2.0 Token Exchange configuration and troubleshooting in Keycloak 14. The core goal is to exchange external JWT tokens issued by WSO2 for Keycloak access tokens, solving cross-identity-provider token interoperability challenges. Keywords: Keycloak, Token Exchange, WSO2.

This article provides the minimum viable setup for Keycloak 14 token exchange

Parameter Description
Language Java / Shell
Protocols OAuth 2.0, OpenID Connect, RFC 8693
Version Keycloak 14.0.0
GitHub Stars Not provided in the original article
Core Dependencies Keycloak, WSO2 APIM, external OIDC IdP

Keycloak Token Exchange lets you convert an already issued token into a new token that another context can accept. A common use case appears when a unified identity platform coexists with an API gateway and the system must securely carry user identity across different security domains.

In Keycloak 14, this capability is not available out of the box. Even if you enable token_exchange, you may still not see an obvious entry point in the admin console, because the legacy exchange model depends on fine-grained permissions and also requires management authorization features to be enabled.

Keycloak 14 requires two preview features to be enabled at the same time

# Enable both token exchange and fine-grained admin authorization
-Dkeycloak.profile.feature.admin_fine_grained_authz=enabled \
-Dkeycloak.profile.feature.token_exchange=enabled

These startup parameters explicitly enable the two preview capabilities that token exchange depends on in Keycloak 14.

Successful token exchange depends on both client permissions and external IdP trust

Checking or configuring Token Exchange only on the client side is not enough. If the token to be exchanged comes from an external issuer such as WSO2, Keycloak must recognize that issuer as a trusted Identity Provider. Otherwise, it will reject the external token immediately.

Use this configuration order: enable the features first, then configure client permissions, then add the WSO2 IdP, and finally verify the token exchange flow. This order makes troubleshooting much easier.

Create a token-exchange permission policy for the client

On the target client’s Permissions page, find the token-exchange permission, then create a Client type policy. Add the clients that are allowed to initiate token exchange to the allowlist, and bind that policy to the permission.

# Configuration outline, not a command to execute directly
1. Open client Permissions
2. Find the token-exchange permission
3. Click Create Policy -> choose Client
4. Add the client allowed to exchange tokens, for example wso2
5. Bind the policy and enable the permission

The purpose of this step is to tell Keycloak which clients are allowed to perform token exchange on behalf of a user.

image AI Visual Insight: This screen shows the token exchange capability switch and permission entry on the Keycloak client side. The key point is to explicitly grant the token-exchange permission to specific clients through fine-grained authorization, rather than relying only on basic client settings.

External token issuers must be integrated as OIDC Identity Providers

When WSO2 acts as the external token source, Keycloak must establish trust through Identity Providers. The critical fields here are not the display name, but the consistency of Issuer, Token URL, and the client credentials.

If subject_issuer does not match the iss claim in the external JWT, Keycloak treats the token source as untrusted and returns errors such as Subject token issuer not trusted or Invalid subject token.

image AI Visual Insight: This image shows the basic configuration page for adding an external OIDC Identity Provider in Keycloak. It usually includes Alias, Authorization URL, Token URL, Client ID, and Client Secret. These fields determine whether Keycloak can recognize WSO2 as a trusted issuer.

image AI Visual Insight: This image further highlights the detailed configuration of the external IdP, especially Issuer, authentication method, and token validation settings. For Token Exchange, exact Issuer matching is one of the key requirements for success.

image AI Visual Insight: This screenshot corresponds to the Permission configuration area in either Identity Provider settings or client permissions. It shows that, in addition to integrating the external IdP, you must also authorize specific clients in the permission model to exchange external tokens.

Use the following recommended WSO2 IdP settings

Alias: wso2-apim
Display Name: WSO2 APIM
Authorization URL: https://test-apim.pkulaw.com/oauth2/authorize
Token URL: https://test-apim.pkulaw.com/oauth2/token
Client ID: <wso2-client-id>
Client Secret: <wso2-client-secret>
Issuer: https://test-apim.pkulaw.com

The purpose of this configuration is to let Keycloak validate the token issuer behind WSO2 and establish a trust anchor for token exchange.

The full exchange flow should be validated in three steps instead of all at once

Step 1: let the user obtain a local access token from Keycloak. Step 2: pass that token to WSO2 as an assertion and exchange it for a JWT issued by WSO2. Step 3: ask Keycloak to exchange the WSO2 JWT back into an access token accepted in the local realm.

This three-stage validation method quickly shows whether the issue comes from user authentication, gateway token issuance, or the final exchange endpoint.

Step 1: The user obtains an access token from Keycloak

curl -X POST 'https://kc.com/auth/realms/demoo/protocol/openid-connect/token' \
  -H 'Accept: application/json' \
  --data-urlencode 'grant_type=password' \
  --data-urlencode 'username=test' \
  --data-urlencode 'password=123456' \
  --data-urlencode 'client_id=wso2' \
  --data-urlencode 'client_secret=abf99c64-db24-43fb-b63b-c60213b8052f' \
  --data-urlencode 'scope=openid'  # Must include openid to keep user info and the OIDC flow consistent later

This step verifies that Keycloak user authentication and the client credentials are working correctly.

Step 2: Exchange for a gateway token in WSO2 by using JWT Bearer

curl -X POST 'https://wso2.com/oauth2/token' \
  -H 'Content-Type: application/json' \
  -u '3N5OKJnQozVc8pPKWHSf1CTLgwQa:HEj3QGF3bPxLIJbTpEmanW5mIjEa' \
  --basic \
  -d '{
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "kc-access-token",
    "scope": "openid apim:subscribe"
  }'

The output of this step must be a JWT access token. Otherwise, Keycloak cannot validate it properly as an external JWT in the next step.

Step 3: Use the WSO2 JWT to initiate Token Exchange with Keycloak

curl -X POST 'https://kc.com/auth/realms/fabao/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -u 'wso2:abf99c64-db24-43fb-b63b-c60213b8052f' \
  --basic \
  --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
  --data-urlencode 'subject_token=wso2-jwt-token' \
  --data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
  --data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
  --data-urlencode 'scope=openid' \
  --data-urlencode 'subject_issuer=wso2'

This step performs the actual exchange from an external token to a Keycloak token. The most common mistakes here involve Basic authentication and subject_issuer consistency.

Four high-frequency errors each point to a specific root cause

Client not allowed to exchange usually means the client permission is not configured correctly, or the policy is not bound to token-exchange. Invalid client credentials usually means the request did not pass the Keycloak client identity correctly through Basic authentication.

Audience not found often appears when audience is filled in incorrectly. In this scenario, you can usually omit it, or set it only to the target Keycloak client instead of the WSO2 client ID. user info call failure is usually related to incomplete OIDC scopes, especially a missing openid scope.

Use this checklist for the most common errors

1. token exchange grant is not supported
   Cause: token_exchange and admin_fine_grained_authz are not enabled

2. Client not allowed to exchange
   Cause: the client needs exchange access, but the permission policy or IdP trust is not fully configured

3. user info call failure
   Cause: the external token was created without the openid scope

4. Audience not found
   Cause: audience was incorrectly set to the WSO2 client_id; omit it or change it to the target Keycloak client

5. Invalid client credentials
   Cause: the exchange request did not use Basic authentication to send kc_client_id:client_secret

This checklist covers most failed integration scenarios between Keycloak 14 and WSO2.

Production use requires attention to both security and version limitations

Token Exchange introduces additional validation steps and network calls, so you should evaluate performance impact under high concurrency. At the same time, only trusted clients should be allowed to initiate exchanges, to prevent lateral token abuse.

In addition, the Keycloak 14 implementation still belongs to the older preview model, with weaker observability and more configuration complexity than newer versions. If your project can upgrade, evaluate the Token Exchange mechanism in a later Keycloak release first.

AI Visual Insight: This animated image shows a WeChat sharing prompt from the blog platform. It is unrelated to Token Exchange, OIDC, or Keycloak configuration itself, and should be treated as a page-level UI element rather than a technical implementation detail.

FAQ

Q: Why can’t I exchange tokens even though I already enabled token_exchange?

A: In Keycloak 14, you must also enable admin_fine_grained_authz and create and bind a token-exchange policy in the client Permissions section. Otherwise, the feature does not actually become operational.

Q: Why can WSO2 issue a token, but Keycloak still says the subject token is invalid?

A: Usually because WSO2 did not return a JWT, the token has expired, or subject_issuer does not match the iss claim inside the JWT. Decode the JWT first and verify these three points.

Q: How should I set audience in the request?

A: In this scenario, you usually do not need to set it. If you must set it, use the target Keycloak client, not the WSO2 client ID. If you set it incorrectly, the common error is Audience not found.

Core summary: This article systematically explains the enablement requirements for OAuth 2.0 Token Exchange in Keycloak 14, fine-grained permission configuration, how to integrate WSO2 as an external IdP, and how to diagnose and fix common errors. It is ideal for developers who need to establish a token exchange flow between Keycloak and WSO2/APIM.