-1

I am currently using Keycloak (v11) as an identity broker for authentication and authorization. One issue I am facing is that the JWT tokens generated by Keycloak tend to get very large if a user has many roles. Currently, the project that uses Keycloak for identity brokering consists of multiple micro services (clients, in Keycloak terms). This leads me to ask two questions:

  1. Why is it that a specific client / resource asks for a JWT, the JWT comes with all client roles for that user (including client roles for other clients)? Would it break any pattern in Open ID Connect if I changed the default client scope, so that only the specific client roles related to a client would appear? Or would that specific pattern have a different name?
  2. Is there a OIDC related pattern, where one first authenticates and then "lazy evaluates" authorization related questions like roles? That is, I would like some agent Bob to authenticate via Keycloak, and whenever Bob wants to use some service protected by a role, Bob asks Keycloak whether he has that specific role. The purpose of this would be to minimize the token size.
mstaal
  • 590
  • 9
  • 25
  • I guess you have incorrect client configuration in the Keycloak. Why you don't configure client to return only roles, which are related for that particular client? There is many config options on the client level. Blind guess (because you didn't post any config) `Full Scope Allowed` is `ON`. – Jan Garaj Oct 07 '21 at 15:37
  • What does 'Full Scope Allowed' OFF indicate? – mstaal Oct 08 '21 at 07:23
  • `Full Scope Allowed: OFF` = you can select required roles per client explicitly; `Full Scope Allowed: ON` = client gets all the role mappings of the user implicitly. See doc: https://www.keycloak.org/docs/latest/server_admin/index.html#_role_scope_mappings – Jan Garaj Oct 08 '21 at 07:35
  • I tried that, and it does not seem to alter the behaviour. Possibly because the client roles are defined in terms of composite roles, so that they are automatically inherited from some realm role. – mstaal Oct 08 '21 at 07:52
  • There are many config options and this is only one from many. You didn't show how did you configure your client, so everything is just guess. Unfortunately, I don't want to play guess game. Just tips (but don't blame me if they are not working for your case - again only guesses): scopes, mappers, LDAP filters, ..... – Jan Garaj Oct 08 '21 at 08:04

1 Answers1

0

I ended up making my own custom Keycloak ProtocolMapper that extends from the default UserClientRoleMappingMapper. The trick was to get 'clientId' by means of the ClientSessionContext in the setClaim-method. The default UserClientRoleMappingMapper sort of allows for the same functionality, but you have to manually specify the clientId in the mapper instance. Since this is now done automatically, I remove this option in 'CONFIG_PROPERTIES'.

import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.utils.RoleResolveUtil;

import java.util.ArrayList;
import java.util.List;

public class UserClientRoleMapper extends UserClientRoleMappingMapper {

    public static final String MAPPER_PROVIDER_ID = "oidc-client-role-mapper-scoped";

    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        if (CONFIG_PROPERTIES.size() > 0) {
            return CONFIG_PROPERTIES;
        }
        CONFIG_PROPERTIES.addAll(super.getConfigProperties());
        CONFIG_PROPERTIES.removeIf(property -> property.getName().equals(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_CLIENT_ID));
        return CONFIG_PROPERTIES;
    }

    @Override
    public String getId() {
        return MAPPER_PROVIDER_ID;
    }

    @Override
    public String getDisplayType() {
        return "User Client Role Scoped";
    }

    @Override
    public String getHelpText() {
        return "Map a user client role to a token claim.";
    }

    @Override
    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession session, ClientSessionContext clientSessionCtx) {
        String clientId = clientSessionCtx.getClientSession().getClient().getClientId();
        String rolePrefix = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_CLIENT_ROLE_MAPPING_ROLE_PREFIX);

        if (clientId != null && !clientId.isEmpty()) {
            AccessToken.Access access = RoleResolveUtil.getResolvedClientRoles(session, clientSessionCtx, clientId, false);
            if (access == null) {
                return;
            }

            UserClientRoleMappingMapper.setClaim(token, mappingModel, access.getRoles(), clientId, rolePrefix);
        }
    }

}
mstaal
  • 590
  • 9
  • 25