Documentation for supported token-exchange (#38008)

closes #37126

Signed-off-by: Marek Posolda <mposolda@gmail.com>


Co-authored-by: Bruno Oliveira da Silva <bruno@abstractj.com>
Co-authored-by: andymunro <48995441+andymunro@users.noreply.github.com>
This commit is contained in:
Marek Posolda 2025-03-14 09:55:44 +01:00 committed by GitHub
parent fde3f9de06
commit 290905c9cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 396 additions and 21 deletions

View File

@ -25,6 +25,16 @@ This strengthens a secure-by-default setup and minimizes the configuration steps
For more information, check the link:https://www.keycloak.org/server/caching#_securing_transport_stacks[Securing Transport Stacks] in the distributed caches guide.
= Supported Standard Token Exchange
In this release, we added support for the Standard token exchange! The token exchange feature was in preview for a long time, so we are glad to finally support the standard token exchange.
For now, this is limited to exchanging the Internal token to internal token compliant with the https://datatracker.ietf.org/doc/html/rfc8693[Token exchange specification]. It does not yet cover use
cases related to identity brokering or subject impersonation. We hope to support even more token exchange use cases in subsequent releases.
For more details, see the link:{securing_apps_token_exchange_link}#_standard-token-exchange[Standard token exchange].
For information on how to upgrade from the legacy token exchange used in previous {project_name} versions, see the link:{upgradingguide_link}[{upgradingguide_name}].
= New cache for CRLs loaded for the X.509 authenticator
Now the Certificate Revocation Lists (CRL), that are used to validate certificates in the X.509 authenticator, are cached inside a new infinispan cache called `crl`. Caching improves the validation performance and decreases the memory consumption because just one CRL is maintained per source.

View File

@ -84,6 +84,7 @@ The scope parameter contains the string, with the scope values divided by spaces
== Evaluating Client Scopes
include::proc-evaluating-client-scopes.adoc[]
[[client-scopes-permissions]]
== Client scopes permissions
When issuing tokens to a user, the client scope applies only if the user is permitted to use it.

View File

@ -66,6 +66,8 @@ For client-side clients that perform browser logins. As it is not possible to en
*Service account roles*:: If enabled, this client can authenticate to {project_name} and retrieve access token dedicated to this client. In terms of OAuth2 specification, this enables support of `Client Credentials Grant` for this client.
*Standard Token Exchange*:: If enabled, this client can use the link:{securing_apps_token_exchange_link}#_standard-token-exchange[Standard token exchange].
*Auth 2.0 Device Authorization Grant*:: If enabled, this client can use the OIDC xref:con-oidc-auth-flows_server_administration_guide[Device Authorization Grant].
*OIDC CIBA Grant*:: If enabled, this client can use the OIDC xref:con-oidc-auth-flows_{context}[Client Initiated Backchannel Authentication Grant].

View File

@ -1,4 +1,5 @@
[[managing-user-sessions]]
== Managing user sessions
When users log into realms, {project_name} maintains a user session for each user and remembers each client visited by the user within the session. Realm administrators can perform multiple actions on each user session:

View File

@ -45,4 +45,5 @@ https://www.keycloak.org/observability/*
https://www.keycloak.org/high-availability/concepts-memory-and-cpu-sizing#_measuring_the_activity_of_a_running_keycloak_instance
http://example.com:8080
# To be removed once KC 26.2 has been released
https://www.keycloak.org/server/caching#_securing_transport_stacks
https://www.keycloak.org/server/caching#_securing_transport_stacks
https://www.keycloak.org/securing-apps/token-exchange*

View File

@ -132,3 +132,5 @@
:securing_apps_link: https://www.keycloak.org/guides#securing-apps
:securing_apps_java_policy_enforcer_link: https://www.keycloak.org/securing-apps/policy-enforcer
:securing_apps_java_policy_enforcer_name: Java Policy enforcer
:securing_apps_token_exchange_link: https://www.keycloak.org/securing-apps/token-exchange
:securing_apps_token_exchange_name: Token exchange Documentation

View File

@ -52,6 +52,43 @@ If your deployment scripts add explicit NetworkPolicies for {project_name}, you
Read more about this in the https://www.keycloak.org/operator/advanced-configuration[Operator Advanced configuration].
=== Supported standard token exchange
In this release, {project_name} added support for the link:{securing_apps_token_exchange_link}#_standard-token-exchange[Standard token exchange] (Feature `token-exchange-standard:v2`). In the past {project_name} releases,
{project_name} had only a preview token exchange feature, which is now referred to as link:{securing_apps_token_exchange_link}#_legacy-token-exchange[Legacy token exchange] (Feature `token-exchange:v1`).
The legacy token exchange is still in preview and it works the same way as in previous releases. If you used the link:{securing_apps_token_exchange_link}#_internal-token-to-internal-token-exchange[internal-internal token exchange],
consider migrating to the new standard token exchange.
If you prefer to continue using the legacy token exchange, you will find it operates as in previous releases. No need exists to disable the standard token exchange feature. Your clients will use the standard token exchange only if it is enabled on the {project_name} client. However, migration to the standard token exchange is recommended. It is the officially supported method and the priority for enhancements.
Consider the following notes as you plan for migration to the new standard token exchange:
* The feature `token-exchange-standard`, which represents the new Standard token exchange, is enabled by default. It is recommended to
disable the `token-exchange` feature, which represents the Legacy token exchange, to make sure that requests will be served by the new standard token exchange.
* You can have both the standard and legacy token exchange features enabled, which can be useful if you need to cover standard use cases (internal-internal) together with the other token exchange use cases that are implemented only by legacy token exchange. For instance, link:{securing_apps_token_exchange_link}#_external-token-to-internal-token-exchange[external to internal token exchange] is implemented only by the
legacy token exchange. In this case, {project_name} serves the standard internal-to-internal requests preferably by the standard token exchange while the other requests are served by the legacy token exchange. The choice of standard or legacy token exchange is determined based on the
parameters of the particular request. For example, requests containing non-standard parameters such as `requested_issuer` or `requested_subject` are considered legacy.
* Standard token exchange requires enabling a switch on the client as described in the link:{securing_apps_token_exchange_link}#_standard-token-exchange-enable[{securing_apps_token_exchange_name}].
Consider these additional changes in the behavior of the two types of token exchange:
* Fine-grained admin permissions are no longer needed or supported for the standard token exchange.
* The most notable change regarding the behavior of scopes and audiences is that the applied client scopes are based on the client triggering the token exchange request rather than the "target" client specified by the `audience` parameter.
Support exists for multiple values of the `audience` parameter as mentioned in the specification. The details are described in the link:{securing_apps_token_exchange_link}#_standard-token-exchange-scope[{securing_apps_token_exchange_name}].
* Public clients are no longer allowed to send the token exchange requests. Legacy token exchange allowed public clients to exchange tokens with themselves to downscope the original token. This use case can
instead be covered by using the refresh token grant, in which the `scope` parameter can be used to downscope the refreshed access token, as mentioned in
the https://datatracker.ietf.org/doc/html/rfc6749#section-6[OAuth2 specification].
* Exchanging an access token for a SAML assertion is not supported in this release. In other words, using `requested_token_type=urn:ietf:params:oauth:token-type:saml2` is not supported.
* Exchanging an access token for a refresh token is allowed only if it is explicitly enabled on the client as mentioned in the link:{securing_apps_token_exchange_link}#_standard-token-exchange-details[{securing_apps_token_exchange_name}].
Currently, it is not supported to request offline tokens or exchange a refresh token when the subject token was issued from an offline session. The recommended approach is to exchange for access tokens instead of
refresh token when possible.
=== LDAP provider now can store new users, groups, and roles in a sub-DN of the base DN
When adding new users, groups, or roles, the LDAP provider would always store them in the same base DN configured for the searches. However, in some deployments admins may want to configure a broader DN with `subtree` scope to fetch users (or groups/roles) from multiple sub-DNs, but they don't want new users (or groups/roles) to be stored in this base DN in LDAP. Instead, they would like to chose one of the sub-DNs for that.

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -6,6 +6,320 @@ title="Using token exchange"
priority=120
summary="Configuring and using Token exchange with {project_name}">
Token exchange is the process that allows a client application to exchange one token for another token. In {project_name}, two features implement token exchange:
* <<_standard-token-exchange,Standard token exchange: version 2 (V2)>> - This feature is the fully supported token exchange implementation that is enabled by default once the {project_name} server is started.
* <<_legacy-token-exchange,Legacy token exchange: version 1 (V1)>> - This preview feature is not enabled by default once {project_name} server is started.
The capabilities of {project_name} for token exchange are as follows:
. A client can exchange an existing {project_name} token created for a specific client for a new token targeted to a different client in the same realm.
. A client can exchange an existing {project_name} token for an external token, such as a linked Facebook account.
. A client can exchange an external token for a {project_name} token.
. A client can impersonate a user.
The standard token exchange supports only use-case (1). The legacy token exchange support the four use-cases, but it is a preview 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 {project_name} versions. 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 <<_standard-token-exchange-comparison,token exchange comparison>>.
[[_standard-token-exchange]]
== Standard token exchange
Standard token exchange in {project_name} implements the https://datatracker.ietf.org/doc/html/rfc8693[Token exchange specification]. It allows client application to exchange an existing {project_name} 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.
[[_standard-token-exchange-flow]]
=== Token exchange flow
Consider this typical token exchange flow:
. The user authenticates with the use of the {project_name} SSO to the client application `initial-client`. The token is issued to the `initial-client`.
. 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
. 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 to 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 {project_name} server and use the original token from step 1 as the
_subject token_ and exchange it for another token _requested token_.
. The _requested token_ is returned to `requester-client`. This token can now be sent to the `target-client`.
. 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.
Many other use-cases exist for token exchange, but the preceding example is the most typical.
==== Example token exchange request
The folowing 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`:
[source,bash]
----
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:
[source,json]
----
{
"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
}
----
[[_standard-token-exchange-enable]]
=== 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 <<_standard-token-exchange-flow,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 link:{adminguide_link}#_client-credentials[client authentication method] that is configured
for the client.
.Enabling Token Exchange
image::token-exchange-switch.png[Enabling Token Exchange]
[[_standard-token-exchange-request]]
=== Request and response parameters
The parameters are aligned with the https://datatracker.ietf.org/doc/html/rfc8693#name-token-exchange-request-and-[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 {project_name} 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-details,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 link:{adminguide_link}#_client_scopes[Optional client scopes]
of the `requester-client`. For more details, see <<_standard-token-exchange-scope,scopes and audiences>>. Omitting this parameter means that only
the link:{adminguide_link}#_client_scopes[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 <<_standard-token-exchange-flow,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 <<_standard-token-exchange-scope,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 https://datatracker.ietf.org/doc/html/rfc8693#section-2.2.1[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`
[[_standard-token-exchange-scope]]
=== 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 link:{adminguide_link}#_client_scopes[Default client scopes] of the `requester-client`. When this parameter is used, the effective client scopes are the default scopes together with
the link:{adminguide_link}#_client_scopes[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 link:{adminguide_link}#audience-support[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 way to link:{adminguide_link}#client-scopes-permissions[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.
NOTE: 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.
==== 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 link:{adminguide_link}#_audience_resolve[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 link:{adminguide_link}#_client_scopes[Client scopes] and
link:{adminguide_link}#_audience_resolve[Audience documentation] sections. The response token will be similar to this (claims not interesting for this example omitted for brevity):
[source,json]
----
{
"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 due it contains some client roles, but at the same time, it does not contain any client roles of requested audience client `target-client2`. So token would be like:
[source,json]
----
{
"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.
NOTE: 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.
NOTE: 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 link:{adminguide_link}#_client_scopes_evaluate[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.
[[_standard-token-exchange-details]]
=== 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. The V1 had some very limited support to public clients, when public client can exchange the token to itself with less 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.
* 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
* link:{adminguide_link}#_fine_grain_permissions[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 link:{adminguide_link}#_client_policies[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 link:{adminguide_link}#_transient-session[Transient session] or from an
link:{adminguide_link}#_offline-access[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
image::token-exchange-switch-refresh.png[Enabling refresh token in Token Exchange]
* Token exchange never creates a new link:{adminguide_link}#managing-user-sessions[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).
* {project_name} Token exchange does not yet have support for the `resource` parameter.
* The token exchange specification mentions the concepts of https://datatracker.ietf.org/doc/html/rfc8693#name-delegation-vs-impersonation[impersonation and delegation]. {project_name} has support for the
impersonation use case, but not yet for the delegation use case.
==== 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.
[[_standard-token-exchange-comparison]]
=== 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.
[cols="3*", options="header"]
|===
|Capability |Standard token exchange V2 |Legacy token exchange V1
s|Internal-internal token exchange | Supported. Implemented as per rfc8693 | Preview support. Loose implementation of rfc8693. It is recommended to use V2 instead
s|Allowed `subject_token_type` | Access token type only | Access token type only for internal-internal, JWT for external-internal scenarios
s|Allowed `requested_token_type` | Access token (default), Refresh token, ID token | Access token, Refresh token (default), SAML2 assertion
s|Behaviour 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 scopes of
the "target" client specified by audience parameter. Downscoping support only
s|Behavior of `audience` parameter | Support for more values as per the specification. 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 audience parameter and using scopes of that client
s|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
s|Consents | Allowed for clients with `Consent required` as long as the user is already granted consent | Not allowed for clients with *Consent required*
s|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
s|Revocation chain | Not available for access tokens. Available for refresh tokens | Not available for access nor refresh tokens
s|Delegation per rfc8693|Not supported yet|Not supported
s|Resource parameter per rfc8693|Not supported yet|Not supported
s|Federated token exchange | Not implemented yet | Implemented as a preview
s|Subject impersonation (including direct naked impersonation) | Not implemented yet | Implemented as a preview
|===
[[_legacy-token-exchange]]
== Legacy token exchange
:tech_feature_name: Token Exchange
:tech_feature_id: token-exchange
@ -29,7 +343,7 @@ To use more than the <<_internal-token-to-internal-token-exchange,Internal Token
For details, see the https://www.keycloak.org/server/features[Enabling and disabling features] {section}.
====
== How token exchange works
=== How token exchange works
In {project_name}, 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.
@ -57,7 +371,7 @@ Public clients specify their client identifier as a form parameter. Confidentia
to pass their client id and secret, Basic Auth, or however your admin has configured the client authentication flow in your
realm.
=== Form parameters
==== Form parameters
client_id::
_REQUIRED MAYBE._ This parameter is required for clients using form parameters for authentication. If you are using
@ -97,7 +411,7 @@ scope::
NOTE: 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
==== 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
@ -140,7 +454,10 @@ For simplicity's sake, let's call a token minted by the current realm as an _int
an external realm or identity provider as an _external_ token.
[[_internal-token-to-internal-token-exchange]]
== Internal token to internal token exchange
=== Internal token to internal token exchange
NOTE: For internal token to internal token exchange, it is recommended to use <<_standard-token-exchange,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
@ -150,7 +467,7 @@ need to perform a "permission downgrade" where your app needs to invoke on a les
to propagate your current access token.
[[_client_to_client_permission]]
=== Granting permission for the exchange
==== 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.
@ -195,7 +512,7 @@ Your client now has permission to invoke. If you do not do this correctly, you
try to make an exchange.
[[_internal_internal_making_request]]
=== Making the request
==== 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.
@ -234,7 +551,7 @@ the client making the request to successfully complete the exchange.
}
----
== Internal token to external token exchange
=== Internal token to external token exchange
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
@ -253,7 +570,7 @@ If the account is not linked, the exchange response will contain a link you can
discussed more in the <<_internal_external_making_request, Making the Request>> section.
[[_grant_permission_external_exchange]]
=== Granting permission for the exchange
==== 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.
@ -293,7 +610,7 @@ image::exchange-idp-apply-policy.png[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.
[[_internal_external_making_request]]
=== Making the request
==== 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.
@ -340,7 +657,8 @@ so that the client can perform link:{developerguide_link}[Client Initiated Accou
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
[[_external-token-to-internal-token-exchange]]
=== 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
@ -359,7 +677,7 @@ These types of changes required a configured identity provider in the Admin Cons
NOTE: SAML identity providers are not supported at this time. Twitter tokens cannot be exchanged either.
=== Granting permission for the 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 <<_grant_permission_external_exchange, internal to external permission is granted>>.
@ -368,7 +686,7 @@ If you also provide an `audience` parameter whose value points to a different cl
must also grant the calling client permission to exchange to the target client specific in the `audience` parameter. How
to do this is <<_client_to_client_permission, discussed earlier>> in this section.
=== Making the request
==== 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
@ -411,21 +729,24 @@ an example JSON response you get back from this call.
----
== Impersonation
=== 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.
NOTE: The impersonation scenario mentioned here is different from the https://datatracker.ietf.org/doc/html/rfc8693#name-delegation-vs-impersonation[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
==== Granting permission for the exchange
The user that the subject token represents must have permission to impersonate other users. See the
link:{adminguide_link}[{adminguide_name}] on how to enable this permission. It can be done through a role or through
fine grain admin permissions.
=== Making the request
==== 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.
@ -443,7 +764,7 @@ curl -X POST \
http://localhost:8080{kc_realms_path}/myrealm/protocol/openid-connect/token
----
== Direct Naked Impersonation
=== 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.
@ -454,7 +775,7 @@ is able to authenticate users itself, but not able to obtain a token.
WARNING: 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
==== 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.
@ -504,7 +825,7 @@ try to make this type of exchange.
NOTE: Public clients are not allowed to do direct naked impersonations.
=== Making the request
==== 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.
@ -519,14 +840,14 @@ curl -X POST \
http://localhost:8080{kc_realms_path}/myrealm/protocol/openid-connect/token
----
== Expand permission model with service accounts
=== 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
=== Exchange vulnerabilities
When you start allowing token exchanges, there are various things you have to both be aware of and careful of.