Cross-realm impersonation via token exchange

Apologies if this is already documented, but I’m still cannot find anywhere how to allow for the following scenario, which in my opinion must be quite a normal requirement.

I’m running Keycloak in Docker with the extra parameters:
-Dkeycloak.profile.feature.token_exchange=enabled
-Dkeycloak.profile.feature.admin_fine_grained_authz=enabled

We want to have all our super-users (administrators and support personnel) in the master realm. This works fine from within the Keycloak UI, using the impersonate button, but I have no luck with the token exchange API.

Via the token exchange functionality, we want to exchange a token from the master realm for an access token for the acme realm and the acme-client

Login super-user:

token=$(curl -s -d 'client_id=security-admin-console' -d 'username=admin' -d 
'password=superpassword' -d 'grant_type=password' 
'https://keycloak.example.net/auth/realms/master/protocol/openid-connect/token' | jq -r 
.access_token)

Attempt to exchange token for one in acme realm with acme-client:

curl -s -X POST "https://keycloak.example.net/auth/realms/acme/protocol/openid-connect/token" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d 'client_id=acme-client' \
-d "requested_subject=acme-user" \
-d "subject_token=$token" \
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token"

This gives me an error that my token is invalid.

I then tried to set up the master realm as an identity provider with alias master and added to this parameter to the above request:

-d "subject_issuer=master"

This gives me:
{“error”:“access_denied”,“error_description”:“Client not allowed to exchange”}

I then started searching for issues regarding this, and I found that I needed to enable fine-grained authz and set up a security policy. I cannot find that this makes any difference, and I thought the point was that fine-grained was not cross-realm anyway.

I would be extremely grateful if anyone could provide me with any hint to what I’m missing, a better way of achieving this functionality, or even better a minimalist example. I find other questions resembling this one, but there are no answers, unfortunately.

1 Like