Reputation: 1818
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.
Upvotes: 4
Views: 6680
Reputation: 1818
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.
Upvotes: 1
Reputation: 51543
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.
Upvotes: 3