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
You will need a running instance of Keycloak with an admin user to run these scripts against. You can use Tutorial 1 to get to this point.
The Keycloak must have a realm with two configured clients. (The
keycloak-configuration.sh
script includes the setup for these.)We will also be using some of the functions defined in the
keycloak-configuration-helpers.sh
that was introduced in Tutorial 1.
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.
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:
- REALM_NAME: The name of the realm being used. We use “myrealm”.
- TOKEN_EXCHANGE_POLICY_NAME: The name of the exchange policy we created. This can be anything you want. For example “my-exchange-policy”
- INTERNAL_CLIENT_ID: The clientId of the internal client. We use “internalclient”.
- ORIGINAL_CLIENT_ID: The clientId of the original client. We use “originalclient”.
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:
- When enabling the permissions, use
identity-provider/instances/$IDENTITY_PROVIDER_ALIAS/management/permissions
instead ofclients/$INTERNAL_ID/management/permissions
- To get the TOKEN_EXCHANGE_PERMISSION_POLICY_ID, filter for
token-exchange.permission.idp.'$IDP_ID'
instead oftoken-exchange.permission.client.'$INTERNAL_ID
- To get the CLIENT_RESOURCE_ID, filter for
idp.resource.'$IDP_ID'
instead of `client.resource.‘$INTERNAL_ID’ (and probably call it IDP_RESOURCE_ID instead of CLIENT_RESOURCE_ID).
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.