keycloakimpersonationtoken-exchange

keycloak impersonation via token-exchange does not work without roles info in the token


Our system uses a minimalistic token that does not include realm roles and client roles. Everything worked fine - after token validation we get all the information about user roles and groups from /userinfo. But recently we need to enable token-exchange functionality to use impersonation via Keycloak REST API and we have a problem - endpoint gives a 403 "Client not allowed to exchange" error until put the role information back into the token. Can you tell me if it's a bug or am I doing something wrong? I would like to continue to have a minimalistic token and use the token-exchange functionality.

For reproduce:

  1. set up keycloak 19+ with the token-exchange feature turned on
  2. create realm
  3. create public client
  4. move "roles" from "Default Client Scopes" to "Optional Client Scopes" in the Client Scopes section of new client
  5. create privileged user (aka moderator) with role "realm-management: impersonation"
  6. create regular user
  7. get token for privileged user like:
curl --location --request POST 
'{{host}}/realms/{{realm}}/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=our-public-client' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=moderator' \
--data-urlencode 'password=qwe123' \
  1. send a token exchange request to get a regular user's token, like:
curl --location --request POST '{{host}}/realms/{{realm}}/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=our-public-client' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token={{MODERATOR TOKEN HERE}}' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'requested_subject={{USERNAME OF REGULAR USER}}'

In this scenario I get an error:

{
    "error": "access_denied",
    "error_description": "The client is not allowed to exchange.
}

but if I add roles to the token in step 7 ( add --data-urlencode 'scope=roles') then everything works.


Solution

  • In reply to @dreamcrash Thanks for the detailed analysis! I solved my problem in a slightly different way - I left roles in Client Scopes optional, made two mappers (for client roles and realm roles) - in which I specified that realm roles should be added to the access token, but client roles only to /userinfo. Since moderators are not expected to have any additional roles from realm-management other than impersonate - only it is added to the token. enter image description here