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:
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' \
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:



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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With