Keycloak token exchange across realms

4.5k views Asked by At

We use Keycloak 12.02 for this test.

The idea is that we have a lot of customers, that we all have in their own realms. We want to be able to impersonate a user in any non-master realm for an admin/support user in the master realm.

The flow would be to:

  1. login using a super-user/password to login into the master realm
  2. get a list of all available realms and their users
  3. craft a request to exchange the current access token with a new access token for that specific user.

It is the last step I cannot get to work.

Example:

Login to master realm

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

(we now have an access token for the super-user in the master realm)

The Keycloak server has enabled token exchange (-Dkeycloak.profile.feature.token_exchange=enabled) as described here https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange.

Attempt to impersonate a user in another realm (not master):

curl -s -X POST "https://login.example.net/auth/realms/some_realm/protocol/openid-connect/token" \
 -H "Content-Type: application/x-www-form-urlencoded" \
 --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
 -d 'client_id=some_client' \
 -d "requested_subject=some_user" \
 -d "subject_token=$token"

However, this does not work. The result is: {"error":"invalid_token","error_description":"Invalid token"}

(Doing this inside a single realm work)

What am I doing wrong here? This seems like a very normal feature to utilize in a real-life deployment, so any help is much appreciated!

UPDATE: First of all, I found the very same use-case here: https://lists.jboss.org/pipermail/keycloak-user/2019-March/017483.html

Further, I can get it to work by working through some major hoops. As described above, one can use the broker client in the master realm as an identity provider:

  • Login as super-user adminA -> TokenA
  • use TokenA to get a new external token, TokenExt from the master identity provider.
  • Use TokenExt to do a token exchange for the user you want to impersonate

The caveat with the above is that the user adminA is created in each of the realms you log into with this method, so still not ideal.

1

There are 1 answers

2
JRobinss On

as far as I know what you are describing is not possible. I'm wondering where you are, more than a year afterwards... did you solve your issue?

Before going further, note that I have found Keycloak discourse a good forum for Keycloak questions: https://keycloak.discourse.group/

Second, this is what I understand: for Keycloak, 2 realms or 2 different Keycloaks is the same. There is nothing common, they are 2 completely different id providers. So any reasoning that supposes shared trust or shared users between realms will not work.

For logging in to the other realm, you need a token that is trusted. There is no reason for the other realm to trust the master realm. The way to set that trust is to set up the master realm client as an identity provider to the other realm (I understand that this is what you do not want to do), so that tokens signed by the master realm will be trusted by the other realm.

And once you have that set up, I have not seen any other way of exchanging than having the token exchange create a federated "admin" user in the other realm (I configure it to be created each time from scratch, to avoid any synch). Also, 2 mappings are going to come in to play, the ID provider mapping, and the client mapping, for creating the resulting JWT.

If this doesn't match with your findings, please correct me.

Ah yes: there is also the question of using token exchange as defined in OAuth, with the may_act claim, which would be perfect here. But it would come after the exchange between realms, in addition. See https://datatracker.ietf.org/doc/html/rfc8693#section-4.4

EDIT: to "create the user each time from scratch"

go to "identity providers" / / "settings" select "sync mode" to "force" This is the relevant extrant from the tooltip:

The sync mode determines when user data will be synced using the mappers. Possible values are: 'legacy' to keep the behaviour before this option was introduced, 'import' to only import the user once during first login of the user with this identity provider, 'force' to always update the user during every login with this identity provider.

so when you choose "force", basically the user will be overwritten at each login. Ok so it's not really a creation, but as close as you can get :-) The idea here is to not care about it, which is fine for prototyping. But I guess that in production you may want to optimize this.