Speak at KeycloakCon Europe 2027 in Barcelona! CfP closes October 18 · Save the date: March 15, 2027 · Submit Today →

Configuring and using token exchange

Configure and use token exchange for Keycloak.

Token exchange is the process that allows a client application to exchange one token for another token. In Keycloak, two features implement token exchange:

  • Standard token exchange: version 2 (V2) - This feature is the fully supported token exchange implementation that is enabled by default once the Keycloak server is started.

  • Legacy token exchange: version 1 (V1) - This preview feature is deprecated and not enabled by default once Keycloak server is started. In future versions legacy Token Exchange will be replaced by version 2, JWT Authorization grant and other features.

The capabilities of Keycloak for token exchange are as follows:

  1. A client can exchange an existing Keycloak token created for a specific client for a new token targeted to a different client in the same realm.

  2. A client can exchange an existing Keycloak token for an external token, such as a linked Facebook account.

  3. A client can exchange an external token for a Keycloak token.

  4. A client can impersonate a user.

The standard token exchange supports only use-case (1). The legacy token exchange supports the four use-cases, but it is a preview and deprecated feature. Therefore, the standard token exchange V2 is recommended since it is supported and will be maintained for the future. The legacy token exchange is useful for last three use cases, but it may not be backwards compatible with future Keycloak versions, and it will be finally removed. You can also enable both token exchange features and use them together. For example, you could use both internal-internal exchange provided by V2 together with other use cases that are supported by V1. For more details, see this token exchange comparison.

If you still need legacy token exchange feature, you also need Fine-grained admin permissions version 1 (FGAP:v1) enabled because Fine-grained admin permissions version 2 (FGAP:v2) does not have support for token exchange permissions. This is on purpose because token-exchange is conceptually not really an "admin" permission and hence there is no plan to add token exchange permissions to FGAP:v2.

Standard token exchange

Standard token exchange in Keycloak implements the Token exchange specification. It allows client application to exchange an existing Keycloak token created for a specific client for a new token issued to the client that triggered the token exchange request. Both clients must be in the same realm.

Token exchange flow

Consider this typical token exchange flow:

  1. The user authenticates with the use of the Keycloak SSO to the client application initial-client. The token is issued to the initial-client.

  2. The client initial-client may need to use the REST service requester-client, which requires authentication. So the initial-client sends the access token from step 1 to the requester-client with the use of the token

  3. To serve the request, the requester-client may need to call another service target-client. However it may be unable to use the token sent to it from initial-client. For example:

    • The token has insufficient permissions or scopes.

    • The target-client is not specified as the token audience; the token was intended to be used to invoke requester-client.

    • The token has too many permissions; therefore, the requester-client may not want to share it with the target-client.

    Any of these situations could be the reason to invoke the token exchange. The requester-client may need to send the token exchange request to the Keycloak server and use the original token from step 1 as the subject token and exchange it for another token requested token.

  4. The requested token is returned to requester-client. This token can now be sent to the target-client.

  5. The target-client can fulfill the request and return the response to the requester-client. The requester-client can then follow and return the response to the request from step 2.

Token Exchange Example Flow
Figure 1. Token Exchange Example Flow

Many other use-cases exist for token exchange, but the preceding example is the most typical.

Example token exchange request

The following is an example token exchange request of the client requester-client in the realm test. Note that subject_token is the access token issued to the initial-client:

POST /realms/test/protocol/openid-connect/token
Authorization: Basic cmVxdWVzdGVyLWNsaWVudDpwYXNzd29yZA==
Content-Type: application/x-www-form-urlencoded
Accept: application/json

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=$SUBJECT_TOKEN&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
requested_token_type=urn:ietf:params:oauth:token-type:access_token

The example token exchange response may look like this:

{
  "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
}

How to enable token exchange

For standard token exchange, token-exchange-standard:v2 is enabled by default. However, you also need to enable the Standard token exchange switch for the client that is supposed to send token exchange requests, such as the requester-client from the previous example. Note that requester-client must be a confidential client. Also, as is the case for other grant requests, the token exchange requests must be authenticated by the appropriate client authentication method that is configured for the client.

Enabling Token Exchange
Figure 2. Enabling Token Exchange

Request and response parameters

The parameters are aligned with the Token exchange specification, which are described as follows:

grant_type

REQUIRED. The value of the parameter must be urn:ietf:params:oauth:grant-type:token-exchange.

subject_token

REQUIRED. A security token that represents the identity of the party on behalf of whom the request is being made.

subject_token_type

REQUIRED. This parameter is the type of the token passed in the subject_token parameter. This must be urn:ietf:params:oauth:token-type:access_token when the standard token exchange is being used because Keycloak does not support other types for the standard token exchange.

requested_token_type

OPTIONAL. This parameter represents the type of token that the client wants to exchange for. In this version, only oauth and OpenID Connect token types are supported. The default value for this is urn:ietf:params:oauth:token-type:access_token. Another possible value is urn:ietf:params:oauth:token-type:id_token if the ID token issued to requester-client is requested. The possible value might be also urn:ietf:params:oauth:token-type:refresh_token; in this case, you will receive both an access token and refresh token within the response. However, the refresh token is allowed if the Allow refresh token in Standard Token Exchange client configuration option is enabled as specified in the standard token exchange section.

scope

OPTIONAL. This parameter represents the space-delimited set of OAuth and OpenID Connect scopes that the client is requesting. You can use Optional client scopes of the requester-client. For more details, see scopes and audiences. Omitting this parameter means that only the Default client scopes are effectively used.

audience

OPTIONAL. Audience specifies client_id of the client, which is supposed to be used as the token audience. In the example above, it could be target-client. Multiple values of this parameter are allowed, which means that you want the token to contain multiple audiences to be used by requester-client in multiple different services. For example audience=target-client1&audience=target-client2 can be used in the request. More details in the section about scopes and audiences.

A successful response is returned in the JSON format. It contains similar parameters such as the response from other grants. The following are some token exchange specifics of the more notable parameters:

access_token

The requested access token. Note that if request specified requested_token_type=urn:ietf:params:oauth:token-type:id_token, this parameter may actually contain the ID token instead of access token. This behavior is per the token exchange specification.

refresh_token

The refresh token. It is included just if requested_token_type=urn:ietf:params:oauth:token-type:refresh_token is used and the client has enabled issuing refresh tokens from the token exchange

issued_token_type

The issued requested token type. Same value as requested_token_type used in the request.

token_type

Usually Bearer if issued token type was access token or refresh token. In case of ID token requested, the value is N_A

Scopes and audiences

The scope parameter in the token exchange request has the same meaning as other grants. This parameter is optional. When it is omitted, the effective client scopes used in the request are the Default client scopes of the requester-client. When this parameter is used, the effective client scopes are the default scopes together with the Optional client scopes

By default, the used client scopes will add the audiences to the aud claim of the token based on the used client scopes and client roles as specified in the Audience documentation.

The audience parameter can be used for filtering of audiences, so that the aud claim will contain only the audiences specified by the audience parameter. Similarly the client roles in the token will be filtered and the token will have only the client roles of the clients specified by the audience parameter.

In addition, the audience parameter can be used to potentially filter client scopes as well. It works in a manner that is similar to Client scope permission for users. If the client scope does not contain any client roles (for example, it contains zero roles or it contains only realm roles), no additional filtering occurs for client scopes. However, if the client scope contains any client role mappings, it must include some client roles of the clients requested by the audience parameter. Composite roles are also included for consideration. If the client scope contains no client roles of the clients requested by the audience, the client scope will be filtered.

The audience parameter can be used to filter the audiences that are coming from the used client scopes. However, this parameter will not add more audiences. When the audience parameter is omitted, no filtering occurs. As a result, the audience parameter is effectively used for "downscoping" the token to make sure that it contains only the requested audiences. However, the scope parameter is used to add optional client scopes and hence it can be used for "upscoping" and adding more scopes.
By default, token exchange can be used to request extra scopes and audiences that are not present in the initial subject_token. If, for security reasons, you want to ensure that scopes are limited to the ones already granted to the subject_token, the downscope-assertion-grant-enforcer policy executor can be applied to the client. This executor enforces that only downscoping is allowed for token exchange. See Downscoping and Client Policies chapters in the Server Administration Guide for more information.

Examples

Here are some examples to better illustrate the behavior for scopes and audiences.

Assume we have the realm with:

  • Client target-client1 with the client role target-client1-role

  • Client target-client2 with the client role target-client2-role

  • Client target-client3 with the client role target-client3-role

  • Client scope default-scope1. This client scope has role scope mapping for the client role target-client1/target-client1-role

  • Client scope optional-scope2. This client scope has role scope mapping for the client role target-client2/target-client2-role

  • Client requester-client, which has client scope default-scope1 added as default client scope and scope optional-scope2 added as an optional client scope

  • Authenticated user, who is member of both target-client1-role and target-client2-role

The settings above means that using scope default-scope1 will add the audience target-client1 to the token and using optional-scope2 will add the audience target-client2. This is because of the audience resolving described in the Audience documentation.

Example 1

Token exchange request sent with scope=optional-scope2 and without audience parameter:

There will be no filtering of audience. The scopes and audiences will be resolved as is the case for any other grants as described in the Client scopes and Audience documentation sections. The response token will be similar to this (claims not interesting for this example omitted for brevity):

{
  "azp": "requester-client",
  "scope": "default-scope1 optional-scope2",
  "aud": [ "target-client1", "target-client2" ],
  "resource_access": {
	"target-client1": {
  	  "roles": [ "target-client1-role" ]
	},
	"target-client2": {
  	  "roles": [ "target-client2-role" ]
	}
  },
  ...
}
Example 2

Token exchange request sent with scope=optional-scope2 and with audience=target-client2

Same like previous example, but target-client1 audience and client roles filtered due audience parameter was included, but only with this target-client2 client. The client scope default-scope1 will be also filtered because it contains some client roles but no client roles of the requested audience client target-client2. So the token would be as follows:

{
  "azp": "requester-client",
  "scope": "optional-scope2",
  "aud": [ "target-client2" ],
  "resource_access": {
    "target-client2": {
      "roles": [ "target-client2-role" ]
    }
  },
  ...
}
Example 3

Token exchange request sent with scope=optional-scope2 and with audience=target-client2&audience=target-client3

The target-client3 is not part of the token audience as user does not have any roles. So in this case, the request will be rejected as some of the requested audiences are not available.

As mentioned in the token exchange specification, it is good practice to downscope the token as much as possible and use only the audiences needed. Ideally use a single audience. This strategy increases the probability that request will be allowed.
If you have a more complex deployment with many various scopes and audiences, it can be challenging to model it in an appropriate way. Consider using the Client scopes evaluate tab to test if the token looks as expected for the given user and for the given set of scopes and audiences.

Token exchange - Additional details

These additional points clarify the behavior of token exchange.

  • It is not supported for public clients to send the token exchange requests. Token exchange V1 includes very limited support for public clients, allowing public clients to exchange the token to itself with fewer scopes. This use case can be replaced by refresh token grant.

  • The subject_token sent to the token exchange endpoint must have the requester client set as an audience in the aud claim. Otherwise, the request would be rejected. The only exception is, if client exchanges his own token, which was issued to it. Exchanging to itself might be useful to downscope/upscope the token or filter unneeded token audiences and so on.

  • Sender-constrained tokens (as defined in RFC 7800) can be used as subject_token only when the requesting client is the same client that the token was originally issued for. The client must also provide valid proof of possession: a DPoP proof for DPoP-bound tokens, or the matching client certificate for mTLS-bound tokens. If the client does not match or the proof is missing/invalid, the request is rejected. For more details on DPoP, see Securing applications with Demonstrating Proof-of-Possession (DPoP).

  • Consents - If the requester client has Consent required enabled, the token exchange is allowed only if the user is already granted consent to all requested scopes

  • Fine-grained admin permissions (FGAP) are not needed for the standard token exchange. We plan to eventually integrate with FGAP for the future, but that integration might be available to all grants. It will not be specific only to token exchange as it was in token exchange V1.

  • Integrating token exchange with Client policies is possible. This integration can be useful to address certain use cases. For example, consider the use case to reject the token exchange request if the client requester-client sends the request with scope=some-confidential-scope. In this example, it can be useful to create a client policy condition with combined conditions for client-scope, grant-type and client-roles.

  • Requesting a refresh token is allowed only if the client has the switch Allow refresh token in Standard Token Exchange set to a value other than No (the default value). The switch is available in the Admin Console in the Advanced tab of the OIDC client in the OpenID Connect Compatibility Modes section. The other available value of the switch is Same session, which means that the refresh token is allowed only if the refresh token can use the same user session as the subject token. If that subject token is coming from a Transient session or from an Offline session, the requesting refresh token will not be allowed. Similarly it will not be allowed to request an offline token (using scope=offline_access).

Enabling refresh token in Token Exchange
Figure 3. Enabling refresh token in Token Exchange
  • Token exchange never creates a new user session. In case that requested_token_type is a refresh token, it may eventually create a new client session in the user session for the requester client (if the client session was not yet created).

  • Keycloak Token exchange does not yet have support for the resource parameter.

  • The token exchange specification mentions the concepts of impersonation and delegation. Keycloak has support for impersonation and preview support for delegation through the Token Exchange Delegation feature.

Revocation

Assuming that there is a subject token access-token1 issued to the client initial-client, here are some considerations related to token revocation:

  • For the case when the access-token1 was exchanged to the access-token2 of the client requester-client, the revocation of the access-token1 will not revoke access-token2. Supporting of a "revocation chain" for access tokens would mean quite an overhead. So considering this, the administrator must ensure that access tokens are short-lived and are revoked automatically after some time.

  • For the case when access-token1 was exchanged to refresh-token2 of client requester-client, we try to support revocation chain. This means that:

    • Revocation of access-token1 will revoke also refresh-token2. Moreover this will remove the client session of the client requester-client from the user session and hence all refresh tokens of requester-client in this user session will be effectively revoked

    • In case that refresh-token2 and it’s related access token was used for the further token exchange to different client, then revocation of access-token1 will revoke those subsequent token exchanges as well. In other words, the whole "chain" of exchanged tokens is going to be revoked.

    • Note that the access token should be valid when the revocation endpoint is invoked. If you do not have a valid access token when the original access-token1 has expired, you can potentially use another access token issued to same client in the same user session. The exchanged tokens such as refresh-token2 and others from the "chain" should be revoked.

Comparison of standard token exchange and legacy token exchange

While the preceding sections fully detail standard and legacy token exchange, the following is an overall summary that compares the two token exchange methods.

Capability Standard token exchange V2 Legacy token exchange V1

Internal-internal token exchange

Supported. Implemented as per RFC 8693

Preview support. Loose implementation of RFC 8693. It is recommended to use V2 instead

Allowed subject_token_type

Access token type only

Access token type only for internal-internal, JWT for external-internal scenarios

Allowed requested_token_type

Access token (default), Refresh token, ID token

Access token, Refresh token (default), SAML 2.0 assertion

Behavior of scope parameter

Aligned with other grants. Scope parameter means requesting optional scopes of the client, which sent the token exchange request

Scope parameter based on the scopes of the "target" client specified by audience parameter. Downscoping support only

Behavior of audience parameter

Support for more values as per the specification. It can be used to narrow down the available audiences and keep only the requested audiences. Effectively downscoping the token per the required target audience

Support for single audience value. Token effectively issued to the client requested by the audience parameter and using the scopes of that client

Public clients

Not available. Downscoping implemented by V1 can be replaced by refresh token grant

Available only to exchange token of the client itself. Effectively downscoping support only

Consents

Allowed for clients with Consent required as long as the user is already granted consent

Not allowed for clients with Consent required

Authorization

Verification that the requester client must be in the audience of the subject_token. Integration with client policies. No Fine-grained admin permissions

Based on fine-grained admin permissions version 1

Revocation chain

Not available for access tokens. Available for refresh tokens

Not available for access nor refresh tokens

Sender-constrained tokens (DPoP, mTLS)

Supported. See Securing applications with Demonstrating Proof-of-Possession (DPoP).

Not supported. The exchanged token is issued without any binding.

Delegation per RFC 8693

Preview support via Token Exchange Delegation

Not supported

Resource parameter per RFC 8693

Not supported yet

Not supported

Internal to external Token Exchange

Identity brokering APIs can be used instead. See Identity Brokering APIs for more information.

Implemented as a preview

External to internal Token Exchange

Use-case implemented by Standard Token Exchange V2 and JWT Authorization Grant. See OAuth Identity and Authorization Chaining Across Domains for more information.

Implemented as a preview

Subject impersonation (including direct naked impersonation)

Not implemented yet

Implemented as a preview

Token exchange delegation

Token Exchange Delegation is Preview and is not fully supported. This feature is disabled by default.

To enable start the server with --features=preview or --features=token-exchange-delegation

Token exchange delegation allows a subject (a user) to pre-authorize an actor (an administrator or a client application) to act on their behalf, as described in the delegation semantics of the token exchange specification.

Why use delegation

The specification describes two ways one party can act for another. Suppose Alice, the subject, wants a calendar app, the actor, to book meetings for her:

Impersonation

The app gets a token that simply says "Alice". Her calendar service cannot tell whether Alice or the app made a booking.

Delegation

The app gets a token that says "the calendar app, acting for Alice". The calendar service sees both names, and can log or restrict what the app does on her behalf.

Keycloak implements delegation, through the may_act and act claims.

Handing the user’s own access token to the actor, whether that actor is another user or a client application, is impersonation in effect, and it has significant drawbacks:

  • No audience control. Alice’s token was issued to the app she logged in with, and its aud comes from that client’s audience and role scope mappings. Nothing binds it to the calendar service, so the calendar app can present it to any other service that accepts it.

  • No scope restriction. Alice’s token carries everything she is allowed to do, not just booking meetings. If the calendar app is compromised, the attacker can use it anywhere Alice’s token is accepted.

  • No audit trail. The calendar service sees a request from Alice. It cannot tell the calendar app made it, so a misbehaving app looks exactly like her.

  • No user consent. Alice is never asked whether the calendar app may act for her.

Delegation addresses all of these. The delegated token is issued with the audience and scopes of the requesting client, the client that performs the exchange, narrowing it to only what that client needs. In client delegation, the requesting client is the actor itself. The act claim provides a clear audit trail, and the user must explicitly consent to the delegation.

Delegation types

Keycloak supports two kinds of actor.

Aspect Admin delegation Client delegation

Scope

delegation:user:<username>

delegation:client:<client_id>

Actor

A user, typically an administrator

A client application, through its service account

Use case

Administrator acts on behalf of a user

Application, such as an AI agent, acts on behalf of a user

may_act contains

sub (actor user ID)

sub (service account user ID) and client_id

Enabling delegation

This feature requires the parameterized-scopes feature to be enabled. To enable both features, start Keycloak with:

bin/kc.[sh|bat] start --features=token-exchange-delegation,parameterized-scopes

When the feature is enabled, the delegation client scopes (delegation:user and delegation:client) are automatically created in all realms (new and existing) as Optional client scopes, making them available to all clients by default. No manual client scope assignment is needed.

The client requesting a delegation scope must have Consent required enabled. The delegation scopes are marked as always requiring consent, and Keycloak rejects a request for such a scope from a client that does not require consent. The delegation consent is never stored permanently, so the user approves the delegation on every login session.

Delegation also requires granting a delegation permission through Fine-grained admin permissions version 2 (FGAP V2). Without a matching permission granting the delegate scope, the delegation scope is silently dropped from the request and the may_act claim is not included in the token. The login itself still succeeds.

Delegation permissions require FGAP V2. FGAP V1 does not support delegation.

Granting delegation permission

Unlike impersonation, delegation does not use admin roles. Instead, you configure FGAP V2 permissions to control which actors can be delegated to and for which users.

Granting delegation to all users

This example allows the actor-app client, and only that client, to act on behalf of any user in the realm.

  1. Go to Realm Settings → Admin Permissions and enable Admin Permissions.

  2. Navigate to Admin Permissions.

  3. Create a new permission on the Users resource type.

  4. Add the delegate scope.

  5. Under Enforce access to, leave All users selected. Those are the users that can be acted for.

  6. Under Policies, click Create new policy, choose Client, and select actor-app. The policy is the set of actors allowed to act, so only actor-app can delegate. Adding more clients to the policy widens that set.

If you want to restrict the permission to an administrator instead of a client, choose the User type for the policy in the last step, and select the administrator username.

With this in place, scope=openid delegation:client:actor-app succeeds for any user, while delegation:client:some-other-app is dropped during scope resolution.

Granting delegation to members of a specific group

  1. Navigate to Admin Permissions.

  2. Create a new permission on the Groups resource type.

  3. Add the delegate-members scope.

  4. Under Enforce access to, choose Specific groups and select the target groups.

  5. Under Policies, click Create new policy, or assign an existing one, targeting the administrator or service account allowed to delegate.

Admin delegation

Admin delegation allows a user (subject) to delegate their token to an administrator (actor). The client requests the delegation:user scope with the administrator’s identifier as a parameter using the parameterized scopes syntax.

How it works

  1. The client requests the delegation:user scope with the administrator’s (actor) username as a parameter, for example scope=openid delegation:user:admin. When the realm has Login with email enabled, the administrator’s email can be used instead, for example scope=openid delegation:user:admin@example.com. For users in the built-in store, both lookups are case-insensitive. For users from a custom user storage provider, case sensitivity is determined by that provider.

  2. Keycloak validates that the named user exists, is not a service account, and is not the user who is logging in.

  3. Keycloak validates that the named administrator (actor) has the delegate permission for the user (subject) through FGAP V2.

  4. The user (subject) is presented with a consent screen to approve the delegation.

  5. If the user consents and validation passes, the issued token includes the may_act claim with the actor’s user ID as the sub:

    {
      "sub": "0fa1e5d4-3c2b-4a19-8765-0123456789ab",
      "may_act": {
        "sub": "f1234567-89ab-cdef-0123-456789abcdef"
      },
      "scope": "openid delegation:user:admin",
      ...
    }

The may_act claim declares that the specified administrator (actor) is authorized to act on behalf of the user (subject). The sub inside may_act contains the actor’s user ID, consistent with Keycloak’s standard sub claim semantics. An external Security Token Service (STS) receiving this token can verify the delegation authorization directly from the claim without querying back to Keycloak.

Unlike delegation:client, the delegation:user scope does not add anything to the subject token’s aud claim. If the administrator performs the exchange from a client other than the one the subject token was issued for, that client must still be within the subject token’s audience. Add an Audience mapper to the subject client if that is not already the case.

Adding additional claims to may_act

By default, only the actor’s sub (user ID) is included in the may_act claim. It is produced by the built-in Parameterized Scope User Property mapper named may_act sub, which resolves the scope parameter as a username (or an email, when the realm allows login with email) and maps that user’s ID into the claim. Additional claims can be added by configuring extra Parameterized Scope User Property mappers on the delegation:user client scope in the Admin Console.

For example, to include the actor’s username:

  1. Navigate to Client scopes → delegation:user → Mappers → Add mapper → By configuration → Parameterized Scope User Property.

  2. Set User Attribute to username.

  3. Set Token Claim Name to may_act.preferred_username.

  4. Enable the token types where the claim should be included (e.g. access token, ID token).

The resulting token will then include:

{
  "may_act": {
    "sub": "f1234567-89ab-cdef-0123-456789abcdef",
    "preferred_username": "admin"
  }
}

Other useful claims to add include may_act.email (user attribute email) or may_act.iss (for cross-realm delegation scenarios).

Client delegation

Client delegation allows a user (subject) to delegate their token to a client application (actor), such as an AI agent or automated service. The client requests the delegation:client scope with the actor client’s identifier as a parameter using the parameterized scopes syntax (for example, delegation:client:actor-app).

Unlike admin delegation, which is designed for human administrators, client delegation is designed for application-to-application scenarios where a user consents to let a specific client act on their behalf. The actor client does not need any admin roles or access to the Admin REST API.

Prerequisites

The actor client (the application that will act on behalf of the user) must meet the following requirements:

  • It must be a confidential client.

  • It must have a service account enabled.

  • It must have Standard Token Exchange enabled.

How it works

  1. The client requests the delegation:client scope with the actor client’s client_id as a parameter, for example scope=openid delegation:client:actor-app.

  2. Keycloak validates that the named client exists, is enabled, has service accounts enabled, and that its service account user can be resolved.

  3. Keycloak validates that the actor client’s service account has the delegate permission for the user (subject) through FGAP V2.

  4. The user (subject) is presented with a consent screen to approve the delegation.

  5. If the user consents and validation passes, the issued token includes the may_act claim with the actor’s service account user ID as sub and the client identifier as client_id:

    {
      "sub": "0fa1e5d4-3c2b-4a19-8765-0123456789ab",
      "may_act": {
        "sub": "9b8c7d6e-5f4a-4b2c-9d0e-fedcba987654",
        "client_id": "actor-app"
      },
      "scope": "openid delegation:client:actor-app",
      ...
    }

    The delegation:client scope also automatically adds the actor client to the aud claim of the subject’s token. This is required because the standard token exchange validates that the requester client is present in the subject token’s audience.

  6. The actor client receives the subject’s token (containing may_act) and performs a token exchange at the token endpoint, as described in Performing the delegated exchange.

  7. The resulting delegated token is issued in a transient session (access token only, no refresh token). The token’s aud claim and scopes are resolved from the actor client’s configuration (its client scopes and role mappings), not from the original subject token. This effectively narrows the delegated token to only the audiences and permissions that the actor client is configured for. The token also contains the act claim identifying the actor:

    {
      "sub": "0fa1e5d4-3c2b-4a19-8765-0123456789ab",
      "act": {
        "sub": "9b8c7d6e-5f4a-4b2c-9d0e-fedcba987654",
        "client_id": "actor-app"
      },
      ...
    }

The act claim allows resource servers to identify both who the data belongs to (the subject, via the sub claim) and who is actually making the request (the actor client, via the act claim).

Adding additional claims to may_act

By default, the may_act claim contains the actor’s sub (service account user ID) and client_id. Additional claims can be added by configuring extra mappers on the delegation:client client scope in the Admin Console, in the same way as for admin delegation.

For example, to add a custom claim describing the actor:

  1. Navigate to Client scopes → delegation:client → Mappers → Add mapper → By configuration → Hardcoded claim.

  2. Set Token Claim Name to may_act.custom_value.

  3. Set Claim value to hello.

  4. Enable the token types where the claim should be included (e.g. access token, ID token).

The resulting token will then include:

{
  "may_act": {
    "sub": "9b8c7d6e-5f4a-4b2c-9d0e-fedcba987654",
    "client_id": "actor-app",
    "custom_value": "hello"
  },
  ...
}
Keycloak validates only sub, client_id and iss inside may_act. Every other claim is copied unchanged into the act claim of the delegated token, so it serves to identify and describe the actor for the parties reading the token, never as an input to an authorization decision.
A Hardcoded claim is the same for every actor client, and no mapper resolves an attribute of the actor client itself. Parameterized Scope User Property cannot be reused here, since it treats the scope parameter as a username.

Performing the delegated exchange

Once the subject’s token carries a may_act claim, the actor performs the actual exchange at the token endpoint. This step is the same for admin delegation and client delegation. Only the contents of the may_act claim differ.

The actor_token is an ordinary access token belonging to the actor. Keycloak resolves the user behind it and treats the request as client delegation when that user is a service account, and as admin delegation otherwise. For client delegation, the actor client obtains the token for its own service account with the client_credentials grant:

POST /realms/myrealm/protocol/openid-connect/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=actor-app&
client_secret=XXXXX

For admin delegation, the actor token is the administrator’s own access token, obtained through whichever flow they normally log in with.

The actor then sends both tokens: the subject’s token as the subject_token and its own token as the actor_token. The actor client is confidential, so it authenticates itself on this request as well. The following example uses client_secret_post, but the other client authentication methods work too.

POST /realms/myrealm/protocol/openid-connect/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Accept: application/json

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
client_id=actor-app&
client_secret=XXXXX&
subject_token=eyJhbGci[...redacted...]&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
actor_token=eyJhbGci[...redacted...]&
actor_token_type=urn:ietf:params:oauth:token-type:access_token

The delegation flow uses the following parameters:

Parameter Requirement Value

grant_type

Required

urn:ietf:params:oauth:grant-type:token-exchange

subject_token

Required

The subject’s access token, carrying the may_act claim

subject_token_type

Required

urn:ietf:params:oauth:token-type:access_token

actor_token

Required

The actor’s own access token. Its presence is what selects the delegation flow

actor_token_type

Required

urn:ietf:params:oauth:token-type:access_token

requested_token_type

Optional

urn:ietf:params:oauth:token-type:access_token, which is both the default and the only supported value

Without the actor_token parameter, the request is handled as a standard token exchange, which rejects any subject token carrying a may_act or act claim.

Keycloak then validates both tokens following the delegation semantics of RFC 8693. Unless stated otherwise, a failed check returns 400 Bad Request with the invalid_request error and a description of what failed.

Check Fails when

The actor token is valid

The actor_token cannot be verified, or actor_token_type is not an access token type

The requesting client is entitled to each token

For either token, the requesting client is neither the client the token was issued for nor within its audience. This check returns 403 Forbidden with the access_denied error

The subject and the actor differ

The actor_token resolves to the same user as the subject_token

The subject token carries may_act

The claim is missing, or is not an object

may_act.iss matches the realm

The claim carries an iss that is not this realm’s issuer

may_act.sub names the actor

The claim has no sub, or its sub is not the user resolved from the actor_token

may_act.client_id matches, when present

The claim’s client_id is not the client the actor_token was issued for, or is not the client making the request

The delegated token is issued in a transient session, so no refresh token is returned and the token carries no session identifier. It contains the act claim built from the subject token’s may_act claim.

Restricting the may_act claim with a client policy

The delegation client scopes enforce the permission check and the user consent before adding a may_act claim. A claim with that name can reach a token by other routes though, such as a Hardcoded claim protocol mapper set directly on a client. That claim never passed either check, yet the exchange endpoint would honor it.

The may-act client policy executor closes this gap. It re-validates any may_act claim it finds in a token response, whatever produced it: the issuer must match the realm, the sub must be an existing enabled user other than the token’s own user, that user must hold the delegate permission, and the matching delegation scope must have been consented. Tokens without a may_act claim are untouched, and tokens that fail are rejected with invalid_request.

Configure it under Realm settings → Client policies by adding the may-act executor to a profile and attaching that profile to a policy. All three options are off by default:

Option Effect when switched on

Reject any may_act claim

Rejects every token carrying a may_act claim, for clients that must never take part in delegation

Avoid permission check

Skips the revalidation of the delegate FGAP permission. The may_act validity checks on iss and sub still apply

Avoid consent check

The user’s login consent is still enforced by the normal flows. What is dropped is catching a may_act that a protocol mapper added without the user ever consenting to that delegation

The executor runs on the token response of every grant type, but only acts when that response carries a may_act claim. That is the login and refresh responses where the claim is minted, not the exchange itself, since the delegated token carries act and not may_act. Combine it with the grant-type condition to narrow it further.

Troubleshooting

Delegation scopes fail safe. If the actor is not authorized, the scope is dropped during scope resolution and the login still succeeds, only without a may_act claim in the token.

Symptom Likely cause

Login succeeds, but the token has no may_act claim

The actor lacks the delegate permission, the username or client ID is misspelled, or the actor client has no service account

The authorization request fails with invalid_scope

The requesting client does not have Consent required enabled

The exchange fails with invalid_request

The actor does not match may_act, or the may_act claim is missing or malformed in the subject token

The exchange fails with access_denied

The requesting client is outside the audience of the subject token or of the actor token

A dropped scope is always logged on the server at the WARN level, with the reason:

Invalid scope parameter for 'delegation:client': Client 'actor-app' does not have service accounts enabled in realm 'myrealm'

Two constraints are easy to miss: delegation:user cannot name a service account, so use delegation:client for those, and neither scope can be requested twice with different values in the same request.

Legacy token exchange

Token Exchange Service is Preview and Deprecated. This feature is not fully supported, disabled by default, and will be removed in future versions.

To enable start the server with --features=preview or --features=token-exchange

If you still need legacy token exchange feature, you also need Fine-grained admin permissions version 1 (FGAP:v1) enabled because Fine-grained admin permissions version 2 (FGAP:v2) does not have support for token exchange permissions. This is on purpose because token-exchange is conceptually not really an "admin" permission and hence there is no plan to add token exchange permissions to FGAP:v2.

For details, see the Enabling and disabling features guide.

How token exchange works

In Keycloak, token exchange is the process of using a set of credentials or token to obtain an entirely different token. A client may want to invoke on a less trusted application so it may want to downgrade the current token it has. A client may want to exchange a Keycloak token for a token stored for a linked social provider account. You may want to trust external tokens minted by other Keycloak realms or foreign IDPs. A client may have a need to impersonate a user. Here’s a short summary of the current capabilities of Keycloak around token exchange.

  • A client can exchange an existing Keycloak token created for a specific client for a new token targeted to a different client

  • A client can exchange an existing Keycloak token for an external token, i.e. a linked Facebook account

  • A client can exchange an external token for a Keycloak token.

  • A client can impersonate a user

Token exchange in Keycloak is a very loose implementation of the OAuth Token Exchange specification at the IETF. We have extended it a little, ignored some of it, and loosely interpreted other parts of the specification. It is a simple grant type invocation on a realm’s OpenID Connect token endpoint.

/realms/{realm-name}/protocol/openid-connect/token

It accepts form parameters (application/x-www-form-urlencoded) as input and the output depends on the type of token you requested an exchange for. Token exchange is a client endpoint so requests must provide authentication information for the calling client. Public clients specify their client identifier as a form parameter. Confidential clients can also use form parameters to pass their client id and secret, Basic Auth, or however your admin has configured the client authentication flow in your realm.

Form parameters

client_id

REQUIRED MAYBE. This parameter is required for clients using form parameters for authentication. If you are using Basic Auth, a client JWT token, or client cert authentication, then do not specify this parameter.

client_secret

REQUIRED MAYBE. This parameter is required for clients using form parameters for authentication and using a client secret as a credential. Do not specify this parameter if client invocations in your realm are authenticated by a different means.

grant_type

REQUIRED. The value of the parameter must be urn:ietf:params:oauth:grant-type:token-exchange.

subject_token

OPTIONAL. A security token that represents the identity of the party on behalf of whom the request is being made. It is required if you are exchanging an existing token for a new one.

subject_issuer

OPTIONAL. Identifies the issuer of the subject_token. It can be left blank if the token comes from the current realm or if the issuer can be determined from the subject_token_type. Otherwise it is required to be specified. Valid values are the alias of an Identity Provider configured for your realm. Or an issuer claim identifier configured by a specific Identity Provider.

subject_token_type

OPTIONAL. This parameter is the type of the token passed with the subject_token parameter. This defaults to urn:ietf:params:oauth:token-type:access_token if the subject_token comes from the realm and is an access token. If it is an external token, this parameter may or may not have to be specified depending on the requirements of the subject_issuer.

requested_token_type

OPTIONAL. This parameter represents the type of token the client wants to exchange for. Currently only oauth and OpenID Connect token types are supported. The default value for this depends on whether it is urn:ietf:params:oauth:token-type:refresh_token in which case you will be returned both an access token and refresh token within the response. Other appropriate values are urn:ietf:params:oauth:token-type:access_token and urn:ietf:params:oauth:token-type:id_token

audience

OPTIONAL. This parameter specifies the target client you want the new token minted for.

requested_issuer

OPTIONAL. This parameter specifies that the client wants a token minted by an external provider. It must be the alias of an Identity Provider configured within the realm.

requested_subject

OPTIONAL. This specifies a username or user id if your client wants to impersonate a different user.

scope

OPTIONAL. This parameter represents the target set of OAuth and OpenID Connect scopes the client is requesting. Returned scope is the Cartesian product of scope parameter and access token scope.

We currently only support OpenID Connect and OAuth exchanges. Support for SAML based clients and identity providers may be added in the future depending on user demand.

Responses from a token exchange request

A successful response from an exchange invocation will return the HTTP 200 response code with a content type that depends on the requested-token-type and requested_issuer the client asks for. OAuth requested token types will return a JSON document as described in the OAuth Token Exchange specification.

{
   "access_token" : ".....",
   "refresh_token" : ".....",
   "expires_in" : "...."
 }

Clients requesting a refresh token will get back both an access and refresh token in the response. Clients requesting only access token type will only get an access token in the response. Expiration information may or may not be included for clients requesting an external issuer through the requested_issuer parameter.

Error responses generally fall under the 400 HTTP response code category, but other error status codes may be returned depending on the severity of the error. Error responses may include content depending on the requested_issuer. OAuth based exchanges may return a JSON document as follows:

{
   "error" : "...."
   "error_description" : "...."
}

Additional error claims may be returned depending on the exchange type. For example, OAuth Identity Providers may include an additional account-link-url claim if the user does not have a link to an identity provider. This link can be used for a client initiated link request.

Token exchange setup requires knowledge of fine grain admin permissions (See the Server Administration Guide for more information). You will need to grant clients permission to exchange. This is discussed more later in this chapter.

The rest of this chapter discusses the setup requirements and provides examples for different exchange scenarios. For simplicity’s sake, let’s call a token minted by the current realm as an internal token and a token minted by an external realm or identity provider as an external token.

Internal token to internal token exchange

For internal token to internal token exchange, it is recommended to use Standard token exchange instead of using the legacy token exchange flow described below. Standard token exchange is officially supported.

With an internal token to token exchange you have an existing token minted to a specific client and you want to exchange this token for a new one minted for a different target client. Why would you want to do this? This generally happens when a client has a token minted for itself, and needs to make additional requests to other applications that require different claims and permissions within the access token. Other reasons this type of exchange might be required is if you need to perform a "permission downgrade" where your app needs to invoke on a less trusted app and you don’t want to propagate your current access token.

Granting permission for the exchange

Clients that want to exchange tokens for a different client need to be authorized in the Admin Console. You need to define a token-exchange fine grain permission in the target client you want permission to exchange to.

Target Client Permission
Figure 4. Target Client Permission
Procedure
  1. Toggle Permissions Enabled to On.

    Target Client Exchange Permission Set
    Figure 5. Target Client Permission

    That page displays a token-exchange link.

  2. Click that link to start defining the permission.

    This setup page displays.

    Target Client Exchange Permission Setup
    Figure 6. Target Client Exchange Permission Setup
  3. Click Client details in the breadcrumbs at the top of the screen.

  4. Define a policy for this permission.

  5. Click Authorization in the breadcrumbs at the top of the screen.

  6. Define a policy for this permission.

  7. Click the Policies tab.

  8. Create a Client Policy by clicking Create policy button.

    Client Policy Creation
    Figure 7. Client Policy Creation
  9. Enter in the starting client that is the authenticated client that is requesting a token exchange.

  10. After you create this policy, go back to the target client’s token-exchange permission and add the client policy you just defined.

    Apply Client Policy
    Figure 8. Apply Client Policy

Your client now has permission to invoke. If you do not do this correctly, you will get a 403 Forbidden response if you try to make an exchange.

Making the request

When your client is exchanging an existing token for a token targeting another client, you use the audience parameter. This parameter must be the client identifier for the target client that you configured in the Admin Console.

curl -X POST \
    -d "client_id=starting-client" \
    -d "client_secret=the client secret" \
    --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
    -d "subject_token=...." \
    --data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:refresh_token" \
    -d "audience=target-client" \
    http://localhost:8080/realms/myrealm/protocol/openid-connect/token

The subject_token parameter must be an access token for the target realm. If your requested_token_type parameter is a refresh token type, then the response will contain both an access token, refresh token, and expiration. Here’s an example JSON response you get back from this call.

When the audience parameter is not set, the value of the parameter defaults to the client making the token exchange request.

Unlike with confidential clients, public clients are not allowed to perform token exchanges using tokens from other clients. If you are passing a subject_token, the (confidential) client that was issued the token should either match the client making the request or, if issued to a different client, the client making the request should be among the audiences set to the token.

If you are explicitly setting a target audience (with a client different from the client making the request), you should also make sure that the token-exchange scope permission is configured for the client set to the audience parameter to allow the client making the request to successfully complete the exchange.

{
   "access_token" : "....",
   "refresh_token" : "....",
   "expires_in" : 3600
}

Internal token to external token exchange

For internal token to external token exchange, it is recommended to use Identity Brokering APIs instead. The legacy Token Exchange V1 is deprecated and will be removed in future versions.

You can exchange a realm token for an external token minted by an external identity provider. This external identity provider must be configured within the Identity Provider section of the Admin Console. Currently only OAuth/OpenID Connect based external identity providers are supported, this includes all social providers. Keycloak does not perform a backchannel exchange to the external provider. So if the account is not linked, you will not be able to get the external token. To be able to obtain an external token one of these conditions must be met:

  • The user must have logged in with the external identity provider at least once

  • The user must have linked with the external identity provider through the User Account Service

  • The user account was linked through the external identity provider using Client Initiated Account Linking API.

Finally, the external identity provider must have been configured to store tokens, or one of the above actions must have been performed with the same user session as the internal token you are exchanging.

If the account is not linked, the exchange response will contain a link you can use to establish it. This is discussed more in the Making the Request section.

Granting permission for the exchange

Internal to external token exchange requests will be denied with a 403, Forbidden response until you grant permission for the calling client to exchange tokens with the external identity provider. To grant permission to the client, you go to the identity provider’s configuration page to the Permissions tab.

Identity Provider Exchange Permission
Figure 9. Identity Provider Permission
Procedure
  1. Toggle Permissions Enabled to On.

    Identity Provider Exchange Permission Set
    Figure 10. Identity Provider Permission

    The page displays token-exchange link.

  2. Click the link to start defining the permission.

    This setup page appears.

    Identity Provider Exchange Permission Setup
    Figure 11. Identity Provider Exchange Permission Setup
  3. Click Client details in the breadcrumbs at the top of the screen.

  4. Click Policies tab to create a client policy.

    Client Policy Creation
    Figure 12. Client Policy Creation
  5. Enter the starting client that is the authenticated client that is requesting a token exchange.

  6. Return to the identity provider’s token-exchange permission and add the client policy you just defined.

    Apply Client Policy
    Figure 13. Apply Client Policy

Your client now has permission to invoke. If you do not do this correctly, you will get a 403 Forbidden response if you try to make an exchange.

Making the request

When your client is exchanging an existing internal token to an external one, you provide the requested_issuer parameter. The parameter must be the alias of a configured identity provider.

curl -X POST \
    -d "client_id=starting-client" \
    -d "client_secret=the client secret" \
    --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
    -d "subject_token=...." \
    --data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
    -d "requested_issuer=google" \
    http://localhost:8080/realms/myrealm/protocol/openid-connect/token

The subject_token parameter must be an access token for the target realm. The requested_token_type parameter must be urn:ietf:params:oauth:token-type:access_token or left blank. No other requested token type is supported at this time. Here’s an example of a successful JSON response you get back from this call.

{
   "access_token" : "....",
   "expires_in" : 3600
   "account-link-url" : "https://...."
}

If the external identity provider is not linked for whatever reason, you will get an HTTP 400 response code with this JSON document:

{
   "error" : "....",
   "error_description" : "..."
   "account-link-url" : "https://...."
}

The error claim will be either token_expired or not_linked. The account-link-url claim is provided so that the client can perform Client Initiated Account Linking. Most, if not all, providers require linking through browser OAuth protocol. With the account-link-url just add a redirect_uri query parameter to it and you can forward browsers to perform the link.

External token to internal token exchange

You can trust and exchange external tokens minted by external identity providers for internal tokens. This can be used to bridge between realms or just to trust tokens from your social provider. It works similarly to an identity provider browser login in that a new user is imported into your realm if it doesn’t exist.

The current limitation on external token exchanges is that if the external token maps to an existing user an exchange will not be allowed unless the existing user already has an account link to the external identity provider.

When the exchange is complete, a user session will be created within the realm, and you will receive an access and or refresh token depending on the requested_token_type parameter value. You should note that this new user session will remain active until it times out or until you call the logout endpoint of the realm passing this new access token.

These types of changes required a configured identity provider in the Admin Console.

SAML identity providers are not supported at this time. Twitter tokens cannot be exchanged either.
A combination of Standard Token Exchange V2 and JWT Authorization Grant is recommended instead of the legacy external to internal Token Exchange. See OAuth Identity and Authorization Chaining Across Domains for more information about how those two supported features can be used to substitute the legacy Token Exchange.

Granting permission for the exchange

Before external token exchanges can be done, you grant permission for the calling client to make the exchange. This permission is granted in the same manner as internal to external permission is granted.

If you also provide an audience parameter whose value points to a different client other than the calling one, you must also grant the calling client permission to exchange to the target client specific in the audience parameter. How to do this is discussed earlier in this section.

Making the request

The subject_token_type must either be urn:ietf:params:oauth:token-type:access_token or urn:ietf:params:oauth:token-type:jwt. If the type is urn:ietf:params:oauth:token-type:access_token you specify the subject_issuer parameter and it must be the alias of the configured identity provider. If the type is urn:ietf:params:oauth:token-type:jwt, the provider will be matched via the iss (issuer) claim within the JWT which must be the alias of the provider, or a registered issuer within the provider’s configuration.

For validation, if the token is an access token, the provider’s user info service will be invoked to validate the token. A successful call will mean that the access token is valid. If the subject token is a JWT and if the provider has signature validation enabled, that will be attempted, otherwise, it will default to also invoking on the user info service to validate the token.

The legacy external-to-internal token exchange does not validate the aud (audience) claim of the subject_token, so an ID token issued by the external identity provider to a different client will be accepted as long as signature and expiry checks pass; restrict token-exchange permissions to only the clients that genuinely require them.

By default, the internal token minted will use the calling client to determine what’s in the token using the protocol mappers defined for the calling client. Alternatively, you can specify a different target client using the audience parameter.

curl -X POST \
    -d "client_id=starting-client" \
    -d "client_secret=the client secret" \
    --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
    -d "subject_token=...." \
    -d "subject_issuer=myOidcProvider" \
    --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
    -d "audience=target-client" \
    http://localhost:8080/realms/myrealm/protocol/openid-connect/token

If your requested_token_type parameter is a refresh token type, then the response will contain both an access token, refresh token, and expiration. Here’s an example JSON response you get back from this call.

{
   "access_token" : "....",
   "refresh_token" : "....",
   "expires_in" : 3600
}

Impersonation

For internal and external token exchanges, the client can request on behalf of a user to impersonate a different user. For example, you may have an admin application that needs to impersonate a user so that a support engineer can debug a problem.

The impersonation scenario mentioned here is different from the impersonation concept of the token exchange specification. The specification does not support impersonating the token subject to different subject. The specification semantics rather means "impersonating the client" instead of "impersonating the user".

Granting permission for the exchange

The user that the subject token represents must have permission to impersonate other users. See the Server Administration Guide on how to enable this permission. It can be done through a role or through fine grain admin permissions.

Making the request

Make the request as described in other chapters except additionally specify the requested_subject parameter. The value of this parameter must be a username or user id.

curl -X POST \
    -d "client_id=starting-client" \
    -d "client_secret=the client secret" \
    --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
    -d "subject_token=...." \
    --data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
    -d "audience=target-client" \
    -d "requested_subject=wburke" \
    http://localhost:8080/realms/myrealm/protocol/openid-connect/token

Direct Naked Impersonation

You can make an internal token exchange request without providing a subject_token. This is called a direct naked impersonation because it places a lot of trust in a client as that client can impersonate any user in the realm. You might need this to bridge for applications where it is impossible to obtain a subject token to exchange. For example, you may be integrating a legacy application that performs login directly with LDAP. In that case, the legacy app is able to authenticate users itself, but not able to obtain a token.

It is very risky to enable direct naked impersonation for a client. If the client’s credentials are ever stolen, that client can impersonate any user in the system.

Granting permission for the exchange

If the audience parameter is provided, then the calling client must have permission to exchange to the client. How to set this up is discussed earlier in this chapter.

Additionally, the calling client must be granted permission to impersonate users.

Procedure
  1. Click Users in the menu.

  2. Click the Permissions tab.

    User Permissions
    Figure 14. User Permissions
  3. Toggle Permissions Enabled to On.

    Users Impersonation Permission Set
    Figure 15. Identity Provider Permission

    The page displays an impersonate link.

  4. Click that link to start defining the permission.

    This setup page displays.

    Users Impersonation Permission Setup
    Figure 16. Users Impersonation Permission Setup
  5. Click Client details in the breadcrumbs at the top of the screen.

  6. Define a policy for this permission.

  7. Go to the Policies tab and create a client policy.

    Client Policy Creation
    Figure 17. Client Policy Creation
  8. Enter the starting client that is the authenticated client that is requesting a token exchange.

  9. Return to the users' impersonation permission and add the client policy you just defined.

    Apply Client Policy
    Figure 18. Apply Client Policy

Your client now has permission to impersonate users. If you do not do this correctly, you will get a 403 Forbidden response if you try to make this type of exchange.

Public clients are not allowed to do direct naked impersonations.

Making the request

To make the request, simply specify the requested_subject parameter. This must be the username or user id of a valid user. You can also specify an audience parameter if you wish.

curl -X POST \
    -d "client_id=starting-client" \
    -d "client_secret=the client secret" \
    --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
    -d "requested_subject=wburke" \
    http://localhost:8080/realms/myrealm/protocol/openid-connect/token

Expand permission model with service accounts

When granting clients permission to exchange, you don’t necessarily manually enable those permissions for each and every client. If the client has a service account associated with it, you can use a role to group permissions together and assign exchange permissions by assigning a role to the client’s service account. For example, you might define a naked-exchange role and any service account that has that role can do a naked exchange.

Exchange vulnerabilities

When you start allowing token exchanges, there are various things you have to both be aware of and careful of.

The first is public clients. Public clients do not have or require a client credential in order to perform an exchange. Anybody that has a valid token will be able to impersonate the public client and perform the exchanges that public client is allowed to perform. If there are any untrustworthy clients that are managed by your realm, public clients may open up vulnerabilities in your permission models. This is why direct naked exchanges do not allow public clients and will abort with an error if the calling client is public.

It is possible to exchange social tokens provided by Facebook, Google, etc. for a realm token. Be careful and vigilante on what the exchange token is allowed to do as it’s not hard to create fake accounts on these social websites. Use default roles, groups, and identity provider mappers to control what attributes and roles are assigned to the external social user.

Direct naked exchanges are quite dangerous. You are putting a lot of trust in the calling client that it will never leak out its client credentials. If those credentials are leaked, then the thief can impersonate anybody in your system. This is in direct contrast to confidential clients that have existing tokens. You have two factors of authentication, the access token and the client credentials, and you’re only dealing with one user. So use direct naked exchanges sparingly.

On this page