Tutorial 2 - Configuring Token Exchange using the CLI

What are we doing?

A token exchange means that Keycloak receives a request that already contains an access token and has grant type token-exchange.
Keycloak will verify the access token and respond with one of its own.

Keycloak states

“Token Exchange is Technology Preview and is not fully supported. This feature is disabled by default.”

This tutorial shows you how to enable and configure this feature anyway using the command line interface of Keycloak.

Why are we doing this?

Having Keycloak doing a token exchange can be useful in different scenarios. You can see some examples in the Keycloak documentation

We will look at the case where Keycloak exchanges a token it created for one client (that we will call original client) for a token of a different client (that we will call internal client), since this is the easiest setup.
Keycloak calls this internal to internal exchange in its documentation. If you are interested in a one of the other scenarios, take a look at this chapter below for some hints.

Unfortunately, at the time of this writing, configuring Keycloak to support token exchange is not as straight forward as one might hope.
Here is the official manual on how to do it in the admin console.
Translating this to a CLI script is not trivial, so we created this tutorial for you.

Requirements

Steps

Links to files with all the script snippets described in this step by step explanation plus additional code to setup the necessary realms and clients can be found here.

  1. Enabling token exchange in Keycloak
  2. Enabling token exchange permissions in the client

Step 1: Enabling token exchange in Keycloak

To be able to configure the special permissions needed for token-exchange, Keycloak must be started with the following options.

-Dkeycloak.profile.feature.token_exchange=enabled -Dkeycloak.profile.feature.admin_fine_grained_authz=enabled

If your Keycloak is already running, you have to stop and restart it with these options.

Step 2: Enabling token exchange permissions in the client

For client original client to be able to request a token-exchange for a token for a different client internal client, we must add a permission to the internal client allowing the original client to do that.

This is done in two steps.

We will be using the following variables in the script:

Step 2a: Enabling permissions for the internal client

This step enables the token-exchange permission for the internal client. INTERNAL_ID is the id (not clientId!) of the internal client.

# get ID of internal client
INTERNAL_ID=$(getClient $REALM_NAME $INTERNAL_CLIENT_ID)
# turn on permissions for internal client
$KCADM update clients/$INTERNAL_ID/management/permissions -r "$REALM_NAME" -s enabled=true

Please note that this enables more permissions than just token exchange but none of them do anything without at least one policy that activates them, so we can just ignore them.

Step 2b: Adding a policy to the permission

The permission we just created is not doing much yet. We still have to add a policy stating that the permission is active for our original client.

Keycloak stores the configuration for the permissions and policies in a special client called realm-management. Therefore, we will need that clients’ id in the next steps.

REALM_MANAGEMENT_CLIENT_ID=$(getClient "$REALM_NAME" realm-management)

With this we can create the policy that will make the token-exchange permission apply to our original client. Note ORIGINAL_CLIENT_ID in the clients field.

#create token-exchange client policy
$KCADM create clients/$REALM_MANAGEMENT_CLIENT_ID/authz/resource-server/policy -r "$REALM_NAME" -f - <<EOF
{
"id": "a18a9428-261b-465d-a771-9a23a108cc92",
"name": "$TOKEN_EXCHANGE_POLICY_NAME",
"type": "client",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"clients": "[\"$ORIGINAL_CLIENT_ID\"]"
}
}

EOF

This creates a generic client policy that says that if the client is $ORIGINAL_CLIENT_ID you are allowed to do the thing a permission this policy is applied to is guarding. It could be applied to any permission that can handle a client permission.

The next step is to update the permission we created in step 2a to apply the client policy we just created.

Unfortunately this is where it gets a bit confusing. First we have to gather some ids:

# The id of the token exchange scope
TOKEN_EXCHANGE_SCOPE_ID=$($KCADM get clients/$REALM_MANAGEMENT_CLIENT_ID/authz/resource-server/scope -r "$REALM_NAME" | jq -r '.[] | select(.name==("token-exchange")) | .id')

# The id of the policy we created above.
EXCHANGE_POLICY_ID=$($KCADM get clients/$REALM_MANAGEMENT_CLIENT_ID/authz/resource-server/policy -r "$REALM_NAME" | jq -r '.[] | select(.name=="'$TOKEN_EXCHANGE_POLICY_NAME'") | .id')

# The id of a special policy that was created by Keycloak.
TOKEN_EXCHANGE_PERMISSION_POLICY_ID=$($KCADM get clients/$REALM_MANAGEMENT_CLIENT_ID/authz/resource-server/policy -r "$REALM_NAME" | jq -r '.[] | select(.name | startswith("token-exchange.permission.client.'$INTERNAL_ID'")) | .id')

# The id of the resource representing the client we want to get an exchanged token for.
CLIENT_RESOURCE_ID=$($KCADM get clients/$REALM_MANAGEMENT_CLIENT_ID/authz/resource-server/resource -r "$REALM_NAME" | jq -r '.[] | select(.name | startswith("client.resource.'$INTERNAL_ID'")) | ._id')

These ids are then used to tie it all together. Though not obvious from looking at the admin console, Keycloak stores the information on how the policy, resource and permission work together in an object stored at “authz/resource-server/permission/scope” in the realm-management client.
It was created when we enabled permissions for the internal client and must be updated as follows:

$KCADM update clients/$REALM_MANAGEMENT_CLIENT_ID/authz/resource-server/permission/scope/$TOKEN_EXCHANGE_PERMISSION_POLICY_ID -r "$REALM_NAME" \
-s 'scopes=["'$TOKEN_EXCHANGE_SCOPE_ID'"]' \
-s 'resources=["'$CLIENT_RESOURCE_ID'"]' \
-s 'policies=["'$EXCHANGE_POLICY_ID'"]'

This is it. The token-exchange should work now.

Testing setup

To test your setup you can use the following curl commands.

ORIGINAL_ACCESS_TOKEN=$(curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'grant_type=client_credentials&client_id='"$ORIGINAL_CLIENT_ID"'&client_secret='"$ORIGINAL_CLIENT_SECRET" \
"http://localhost:8080/auth/realms/$REALM_NAME/protocol/openid-connect/token" \
| jq -r '
.access_token')



curl -X POST \
-d "client_id=$ORIGINAL_CLIENT_ID" \
-d "client_secret=$ORIGINAL_CLIENT_SECRET" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=$ORIGINAL_ACCESS_TOKEN" \
-d "subject_issuer=http://localhost:8080/auth/realms/%REALM_NAME" \
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
-d "audience=$INTERNAL_CLIENT_ID" \
http://localhost:8080/auth/realms/$REALM_NAME/protocol/openid-connect/token

If you used the keycloak-configuration.sh script to set up your realm and clients, you can find the ORIGINAL_CLIENT_ID, ORIGINAL_CLIENT_SECRET, INTERNAL_CLIENT_ID and REALM_NAME in there.

Configuring token for an identity provider

If you want to exchange a token from an external source the token exchange configuration is almost the same. (See the internal to external and external to internal examples in the Keycloak documentation)
You just have to create an identity provider for that external source and set the permission on that identity provider instead of the internal client. In detail, it means you have to change the following things in our scripts:

Note: You can use the function getIdentityProviderId() in our keycloak-configuration-helpers.sh to get the IDP_ID.

Files

Run keycloak-configuration.sh to set everything up.

References