Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 424
zo0M Avatar asked Oct 17 '25 09:10

zo0M


1 Answers

Can you tell me if it's a bug or am I doing something wrong?

This seems to be expected behavior.

If you have a look at the Keycloak server-side logs after performing the token exchange using your setup you can see an error message like:

"....type=TOKEN_EXCHANGE_ERROR, .... error=not_allowed, reason='subject not allowed to impersonate'....

and the Rest API call gets on the client-side gets:

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

if you have a look at the Keycloak open source code (in this line)

        event.detail(Details.IMPERSONATOR, tokenUser.getUsername());
        // for this case, the user represented by the token, must have permission to impersonate.
        AdminAuth auth = new AdminAuth(realm, token, tokenUser, client);
        if (!AdminPermissions.evaluator(session, realm, auth).users().canImpersonate(requestedUser, client)) {
            event.detail(Details.REASON, "subject not allowed to impersonate");
            event.error(Errors.NOT_ALLOWED);
            throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN);
        }

You can infere that the error comes from the fact that your client named 'our-public-client' omites a token without the claim resource_access.realm-management.roles.impersonation, which leads the code above to throw the exception:

CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN);

I would like to continue to have a minimalistic token and use the token-exchange functionality.

One solution is:

  • to keep the 'roles' in the 'Client Scopes';

enter image description here

  • in the 'Scope' tab set 'Full Scope Allowed' to 'OFF';

enter image description here

  • in the 'Client Roles' select the client 'realm-management' and the role 'impersonation'.

enter image description here

With this solution, the original token will contain one single role (i.e., impersonation), and the token resulted from the token exchange will still contain no roles.

like image 82
dreamcrash Avatar answered Oct 22 '25 04:10

dreamcrash



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!