Keycloak Token Exchange: How It Works (V2 vs V1)

Guilliano Molaire Guilliano Molaire Updated October 2, 2026 14 min read
Keycloak Token Exchange: How It Works

What is token exchange in Keycloak?

Token exchange in Keycloak lets a confidential client (one that has its own credentials) send an access token it received to the realm’s token endpoint and get back a new token for the same user, usually with a narrower audience or different scopes. It implements RFC 8693, and since Keycloak 26.2 the standard version (V2) is fully supported and enabled by default.

The typical reason you reach for it is a chain of services. A user signs in to a frontend, the frontend calls an API with the user’s access token, and that API then needs to call a second API on the user’s behalf. Forwarding the original token is tempting, but that token was issued for the first API, so it may name the wrong audience, carry more permissions than the second API should see, or lack a scope the second API requires. Token exchange lets the first API ask Keycloak for a token that is shaped for the next hop instead.

This guide explains how the exchange works in Keycloak 26.x, how the standard V2 implementation differs from the older V1 preview, which parameters matter, and what to use for the cases V2 deliberately does not cover. If you want working Node.js and Java code, our practical Keycloak token exchange guide is the hands-on companion to this one.

Token exchange components: who takes part

A token exchange always involves the same few parties. The terms below come from RFC 8693 and from the Keycloak token exchange documentation, which uses the client names initial-client, requester-client and target-client in its examples.

  • The subject token is the token you already hold and want to exchange. In standard token exchange it must be a Keycloak access token.
  • The requested token is the new token Keycloak issues in return.
  • The requester client is the confidential client that sends the exchange request and receives the new token.
  • The target client (the audience) is the service the new token is meant for.
Participant Example Role in the exchange
User (the subject) Alice The person the tokens represent. Her identity carries through the exchange unchanged.
Initial client initial-client, a web frontend Received the original access token when Alice signed in.
Requester client requester-client, an order API Receives Alice’s token, then asks Keycloak to exchange it. Must be a confidential client.
Target client target-client, a billing API The audience of the new token.
Keycloak The realm’s token endpoint Validates the request, applies the realm’s rules and issues the requested token.

Standard token exchange (V2) vs legacy token exchange (V1)

Keycloak has two token exchange implementations, and most of the confusion online comes from blog posts written about the older one. Legacy token exchange (V1) was a preview feature for years and covered four use cases loosely based on the RFC. Standard token exchange (V2) became fully supported in Keycloak 26.2 and follows RFC 8693 closely, but it only covers exchanging one internal Keycloak token for another in the same realm. The other use cases now have dedicated features.

Capability Standard token exchange V2 Legacy token exchange V1
Support status in 26.x Supported, enabled by default (token-exchange-standard:v2) Preview and deprecated, disabled by default (token-exchange)
Internal token to internal token, same realm Supported, aligned with RFC 8693 Preview, loose implementation of the RFC
Allowed subject_token_type Access token only Access token for internal exchange, JWT for external-to-internal
Allowed requested_token_type Access token (default), refresh token, ID token Access token, refresh token (default), SAML 2.0 assertion
How it is authorized The requester client must be in the subject token’s aud claim, plus client policies Fine-grained admin permissions V1
Public clients Not allowed Only to exchange the client’s own token
External token to internal token Use the JWT Authorization Grant (combined with V2 for the Keycloak-to-Keycloak case) Preview
Internal token to external token Use the Identity Brokering APIs instead Preview
Impersonating a user Not implemented Preview
Sender-constrained tokens (DPoP, mTLS), tied to a key or certificate the client must prove it holds Supported Not supported

The Keycloak team recommends V2 for new work, because V1 may not stay backwards compatible and will eventually be removed. You can run both at once if you still need a V1-only use case, but note that V1 depends on fine-grained admin permissions version 1 (admin-fine-grained-authz:v1), which is itself deprecated. Fine-grained admin permissions V2 does not include token exchange permissions, and the Keycloak documentation says there is no plan to add them.

How token exchange works, step by step

Here is the flow in the standard V2 implementation, using the example parties from the table above.

  1. Alice signs in to initial-client through Keycloak, and Keycloak issues an access token whose audience includes requester-client.
  2. initial-client calls requester-client and sends Alice’s access token as a bearer token.
  3. requester-client needs to call target-client, so it sends a token exchange request to the realm’s token endpoint. It authenticates as itself with its own client credentials and passes Alice’s token as the subject_token.
  4. Keycloak validates the subject token (signature, expiry, and that requester-client appears in its aud claim), checks that the Standard Token Exchange switch is on for requester-client, and evaluates any client policies that apply.
  5. Keycloak resolves the scopes and audiences for the new token from the scope and audience parameters, then signs and returns the requested token.
  6. requester-client calls target-client with the new token, and target-client validates it like any other access token.

Two details from the Keycloak documentation are worth knowing before you design around this flow. Token exchange never creates a new user session, and a client is allowed to exchange a token that was issued to itself, which is a simple way to drop audiences or scopes it no longer needs.

Example token exchange request and response

The request is a normal form POST to the realm’s OpenID Connect token endpoint. In Keycloak 17 and later there is no /auth/ prefix in the path, so for a realm called test the endpoint is /realms/test/protocol/openid-connect/token. This example has requester-client exchanging Alice’s token for one scoped to target-client:

curl -X POST "https://keycloak.example.com/realms/test/protocol/openid-connect/token" 
  -u "requester-client:$CLIENT_SECRET" 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" 
  -d "subject_token=$SUBJECT_TOKEN" 
  -d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" 
  -d "requested_token_type=urn:ietf:params:oauth:token-type:access_token" 
  -d "audience=target-client"

A successful response looks like the response from any other grant, with one extra field, issued_token_type, that tells you what kind of token came back:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsIn...",
  "expires_in": 300,
  "token_type": "Bearer",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "session_state": "287f3c57-32b8-4c0f-8b00-8c7db231d701",
  "scope": "default-scope1",
  "refresh_expires_in": 0,
  "not-before-policy": 0
}

You will notice there is no refresh token here. Standard token exchange returns only an access token by default, and it will only return a refresh token if you ask for one and the client is configured to allow it, which is covered in the enablement steps below.

Token exchange request parameters

The parameters follow RFC 8693. This table describes how Keycloak’s standard token exchange treats each one.

Parameter Required What it does in Keycloak V2
grant_type Yes Must be urn:ietf:params:oauth:grant-type:token-exchange.
subject_token Yes The token being exchanged. The requester client must be in its aud claim, unless the client is exchanging its own token.
subject_token_type Yes Must be urn:ietf:params:oauth:token-type:access_token. V2 accepts no other type.
requested_token_type No Defaults to an access token. You can also ask for ...:refresh_token (you get an access token and a refresh token) or ...:id_token.
scope No Adds optional client scopes of the requester client on top of its default client scopes.
audience No One or more client_id values. Filters the token down to only these audiences. It can be repeated.

Client authentication is not a separate parameter here, because the exchange uses whatever authentication method the requester client is configured with, such as a client secret sent with HTTP Basic or as client_id and client_secret form fields, a signed JWT, or mTLS.

How audience and scope shape the exchanged token

The scope and audience parameters work in opposite directions, and it helps to keep that straight. scope can add the requester client’s optional client scopes, so it can widen the token. audience can only remove audiences, so it narrows the token. When you pass audience=target-client, Keycloak keeps only that audience in the aud claim, keeps only that client’s roles under resource_access, and drops client scopes whose client role mappings do not include any role of target-client.

The audience parameter never adds an audience that the scopes and role mappings would not already produce. If you request an audience the user has no roles for, Keycloak rejects the whole request, so the documentation recommends requesting as few audiences as you need, ideally one. If you are not sure how your client scopes and role mappings combine, our guide to client scopes vs roles in Keycloak walks through how audiences end up in a token, and the Client scopes evaluate tab in the admin console shows you the resulting token before you write any code. Once you have a real exchanged token, pasting it into our JWT token analyzer is a quick way to confirm the aud and resource_access claims came out the way you intended.

By default an exchange can request scopes and audiences that were not in the original subject token. If you want to guarantee that an exchange can only narrow a token, Keycloak 26.5 and later have a client policy executor called downscope-assertion-grant-enforcer that you can apply to the requester client so that only downscoping is allowed.

How do I enable token exchange in Keycloak?

The feature itself is already on in Keycloak 26.2 and later, so enabling standard token exchange is a per-client setting.

  1. In the admin console, open the client that will send exchange requests (the requester client, not the target). It must be a confidential client, meaning Client authentication is on.
  2. On the client’s Settings tab, in the Capability config section, turn on the Standard Token Exchange switch and save.
  3. If the requester client needs a refresh token from the exchange, go to the Advanced tab, find OpenID Connect Compatibility Modes, and set Allow refresh token in Standard Token Exchange to Same session. The default is No. Keycloak still refuses to issue the refresh token when the subject token came from a transient or offline session, and you cannot request offline_access through an exchange.
  4. Make sure the tokens that reach the requester client actually list it in their aud claim, usually with an audience mapper or a client role mapping on the initial client. This is the step people most often miss, and without it every exchange is rejected.

If you still need a V1-only use case, you have to start the server with the legacy features enabled, for example bin/kc.sh start --features=token-exchange,admin-fine-grained-authz:v1, and then grant exchange permissions per client or identity provider through fine-grained admin permissions V1. Keycloak only runs one version of fine-grained admin permissions at a time, so enabling V1 for legacy token exchange turns off fine-grained admin permissions V2, and the Permissions section that comes with it, across the whole server. The Keycloak features guide lists the feature names and their support levels.

Types of token exchange

RFC 8693 is a general framework, and Keycloak has historically used “token exchange” to cover four quite different jobs. In 26.x each of them has a clear recommended path.

Internal token exchange

This is the core V2 use case described above: one Keycloak token exchanged for another token for a different client in the same realm. It is how you give a downstream microservice a token with only the audience and roles it needs, and it is what most people searching for “Keycloak token exchange” actually want. Note that V2 does not exchange tokens across realms, because both clients must be in the same realm.

External token exchange

Exchanging a token from an external identity provider for a Keycloak token was a V1 preview feature. In current Keycloak the supported replacement is the JWT Authorization Grant from RFC 7523, which lets a client present a signed JWT from a trusted identity provider and receive a Keycloak access token for the linked user. It became fully supported in Keycloak 26.6, and we walk through a working setup in JWT authorization grant in Keycloak with an external IdP. Keycloak also documents combining standard token exchange in one realm with the JWT Authorization Grant in another to pass a user across trust domains, which is the supported way to do what people used to call cross-realm token exchange.

The opposite direction, getting an external provider’s token (for example a linked Google or GitHub account) from a Keycloak token, is handled by the Identity Brokering APIs rather than by V2. It works when Keycloak stored the external provider’s tokens during login. If you have not set up brokering yet, our guide to adding GitHub social login with identity brokering shows the basic configuration.

Impersonation and delegation

RFC 8693 distinguishes impersonation, where the new token simply says “Alice”, from delegation, where the new token says “this service, acting for Alice” through an act claim. Standard token exchange V2 does not implement impersonation, and V1 impersonation is a preview feature that depends on the deprecated legacy feature set. Keycloak 26.7 added an experimental delegation feature (token-exchange-delegation, which also needs parameterized-scopes) in which a user consents to a named administrator acting on their behalf. Keycloak 26.8 promoted it to preview, renamed that scope to delegation:user, added delegation:client so a user can delegate to a client application such as an AI agent, and moved authorization to FGAP V2. Preview features are still not recommended for production; our Keycloak 26.8 token exchange delegation guide covers the details.

If what your team needs is a support engineer seeing what a user sees, that is the admin console impersonation feature rather than token exchange, and we cover how to scope and audit it in secure user impersonation in Keycloak for support teams.

Security considerations for token exchange

Token exchange lets one service turn a token into another token, so a mistake in its configuration can widen access in ways that are hard to spot. These are the points we check first on any realm that uses it.

  • Keep the requester list short. Only turn on Standard Token Exchange for clients that genuinely call downstream services on a user’s behalf. Every client with the switch on can exchange any token that lists it as an audience.
  • Prefer downscoping. Pass an explicit audience, and on Keycloak 26.5 or later consider the downscope-assertion-grant-enforcer client policy so an exchange can never add scopes the user’s original token did not have.
  • Keep access tokens short-lived. Revoking the original access token does not revoke access tokens that were exchanged from it. Keycloak only maintains a revocation chain when the exchange produced a refresh token, so short access token lifetimes are your main control.
  • Watch sender-constrained clients. In 26.7, Keycloak only accepts a DPoP-bound or mTLS-bound subject token when the requester is the client it was issued to and the proof is valid. If you run certificate-bound clients, read our note on CVE-2026-97846, where standard token exchange drops the mTLS binding, and watch the advisory for a fixed release.
  • Audit the exchanges. Keycloak records TOKEN_EXCHANGE and TOKEN_EXCHANGE_ERROR user events, so turn on user events for the realm and alert on unexpected clients or error spikes.

Token exchange with Skycloak

Skycloak runs standard Keycloak, so everything in this guide applies unchanged: you turn on Standard Token Exchange per client in the Keycloak admin console, and the request and response formats are the ones shown above. What we take off your plate is running the cluster itself, and when a Keycloak release that carries a security fix lands, the upgrade is something you start from the cluster’s detail page.

If you still need legacy V1 token exchange, which is off by default, you can switch it on under Advanced Keycloak Configuration on the cluster’s edit page on Launch, Business and Enterprise plans (the cluster restarts to apply it), as described in our cluster documentation. Because V1 also needs fine-grained admin permissions V1, check with us that it is enabled alongside the token exchange feature before you rely on it. The TOKEN_EXCHANGE events from your realms show up in the Skycloak events viewer on plans that include events, so you can review them alongside the rest of your realm activity with our audit logs.

Frequently asked questions

What is the difference between Keycloak token exchange V1 and V2?

V2 (standard token exchange) is the supported implementation that follows RFC 8693 and is enabled by default since Keycloak 26.2, but it only exchanges internal Keycloak access tokens between clients in the same realm. V1 (legacy token exchange) is a deprecated preview feature that also covers external tokens and impersonation. Keycloak recommends V2 for new work, with the JWT Authorization Grant and Identity Brokering APIs covering the external cases.

How do I enable token exchange in Keycloak 26?

You do not need a server flag for standard token exchange, because token-exchange-standard:v2 is on by default. Open the confidential client that will send the exchange request, turn on Standard Token Exchange in its capability settings, and make sure the tokens it receives list it in their aud claim. Legacy V1 still needs --features=token-exchange,admin-fine-grained-authz:v1 at startup.

Does Keycloak support cross-realm token exchange?

Standard token exchange does not, because both clients must be in the same realm. To move a user from one realm or Keycloak server to another, Keycloak documents a chain where standard token exchange in the first realm produces a token for the second, and the JWT Authorization Grant in the second realm, with the first realm configured as an identity provider, turns it into a local access token. Legacy V1 could also do this through external-to-internal exchange, but it is deprecated.

Can Keycloak token exchange impersonate a user?

Not with standard token exchange V2, which does not implement impersonation. Legacy V1 has preview impersonation that depends on deprecated fine-grained admin permissions, and Keycloak 26.8 has a preview delegation feature in which a user consents to a named administrator or client application acting for them, recorded in a may_act claim and then an act claim (details here). For support staff who need to see a user’s session, use the admin console impersonation role instead.

Why does Keycloak reject my token exchange request?

The most common cause is that the requester client is not in the subject token’s aud claim, which Keycloak requires unless the client is exchanging its own token. Other frequent causes are a public client sending the request, the Standard Token Exchange switch being off, a subject_token_type other than access token, or an audience value the user has no roles for. Check the TOKEN_EXCHANGE_ERROR event in the realm’s user events for the exact reason.

What to do next

If you are starting fresh, build on standard token exchange: turn it on for the one or two services that call others on a user’s behalf, pass an explicit audience on every request, and keep access token lifetimes short. If you have V1 code from an older tutorial, map each use case to its replacement (V2 for internal exchange, the JWT Authorization Grant for external tokens, the Identity Brokering APIs for linked provider tokens) before you upgrade further, because V1 is on its way out.

For the implementation details, continue with our practical Keycloak token exchange guide and our guide to validating Keycloak tokens in your APIs. If you would rather not run the Keycloak cluster yourself, you can see what a managed cluster costs on our pricing page.

Managed identity, built on open source

Skycloak runs real upstream Keycloak for you, so you get SSO, MFA and SCIM without operating the infrastructure. Unlimited users on every plan, no fork, and you can export at any time.

Guilliano Molaire
Written by
Founder

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

Start Free Trial Talk to Sales
© 2026 Skycloak. All Rights Reserved. Design by Yasser Soliman