简体   繁体   中英

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.

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';
  • in the 'Scope' tab set 'Full Scope Allowed' to 'ON';
  • in the 'Client Roles' select the client 'realm-management' and the role 'impersonation'.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM