Custom claims are missing in access token when generated using refresh token

We have a set of custom claim which are added in the access token using overridden method transformAccessToken() in the custom token mapper class. Custom protocol mapper has also been added.

This custom claims are displaying in the access token correctly. But, post expiration of access token, it is re-generated using refresh token. In the new access token, custom claims are missing.

are we missing anything in here?

I think you should be overridding the setClaim methods. See the setClaim javadoc for AbstractOIDCProtocolMapper:

Intended to be overridden in ProtocolMapper implementations to add claims to an token.

This is how I have added claims to a token before, and I didn’t encounter the same problem.

1 Like

if possible, could you please provide a screenshot how you are using setClaim() method?

There are some examples in this repo keycloak-orgs/src/main/java/io/phasetwo/service/protocol/oidc/mappers at main · p2-inc/keycloak-orgs · GitHub

The AbstractOrganizationMapper in that directory has the setClaim methods, and you’ll have to look in the 2 subclasses for examples of how to create the claim object.

There are also loads of example in the Keycloak GitHub. Just search for classes that extend AbstractOIDCProtocolMapper and you’ll find them.

1 Like

Hi @xgp ,

I tried to use the setClaim() method as you suggested but it is not getting called when we are trying to generate access token using refresh token.

I can explain you the issue from start,

  1. During initial authentication or login, we are generating access token and adding custom claims in isValid() method by setting attributes in the keycloakSession.
  2. But, while generating new access token using refresh token we are getting all these custom attribute values as NULL as keycloak will generate new session for each request.
  3. Due to this, we are struggling to get the custom claims in the access token in the second request.
  4. Do you have any idea what can be used to set these custom attributes so that it can be used for all the subsequent requests for same user?
  5. We tried using userSession but it is not working as expected? If you have idea about userSession for setting custom attributes, please share.

Below is how we are using setClaim method,

String attribute1 = (String) keycloakSession.getAttribute(attribute1);
.
.
.
at end we are doing,
OIDCAttributeMapperHelper.mapClaim(accessTokenResponse, mappingModel, claimMap);

The mapClaim method uses accessTokenResponse or idToken, so do you know how this will be used? do we need to set accessTokenResponse or idToken during first login?

Without seeing the full code of your Mapper, there’s no way I could help.

Hi,
Please find our mapper class, unable to attach the file

and what is the use of OIDCAttributeMapperHelper.mapClaim() method, did not found much details.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.rar.AuthorizationRequestContext;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**

  • This is the custom token Protocol Mapper class.

  • It implements the following interfaces:

  • OIDCAccessTokenMapper : To add a custom claim in the Access-token

  • OIDCIDTokenMapper : To add a custom claim in the ID-token

  • */
    public class TSTokenMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {

    private static final Logger log = LoggerFactory.getLogger(TSTokenMapper.class);
    public static final String TS_PROTOCOL_PROVIDER_ID = “custom-protocol-mapper”;

    private static final String DEFAULT_CONFIG_VALUE = “TS User Claim”;
    private static final List configProperties = new ArrayList<>();
    private static final String HELP_TEXT = “TS User Claim”;

    private static final String CUSTOM_CLAIM = “TS User Claim”;

    static {
    configProperties.add(new ProviderConfigProperty(ProtocolMapperUtils.USER_ATTRIBUTE,
    ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL, ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT,
    ProviderConfigProperty.STRING_TYPE, DEFAULT_CONFIG_VALUE ));
    OIDCAttributeMapperHelper.addTokenClaimNameConfig(configProperties);
    OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, TSTokenMapper.class);
    }

    /**

    • This method computes the claims in the accessToken. The custom claims can be added in the accessToken.

    • param accessToken Keycloak AccessToken

    • param protocolMapperModel ProtocolMapperModel

    • param keycloakSession KeycloakSession

    • param userSessionModel UserSessionModel

    • param clientSessionContext ClientSessionContext

    • return AccessToken
      */
      Override
      public AccessToken transformAccessToken(AccessToken accessToken, ProtocolMapperModel protocolMapperModel,
      KeycloakSession keycloakSession, UserSessionModel userSessionModel, ClientSessionContext clientSessionContext) {
      final String METHODNAME = “transformAccessToken(AccessToken accessToken, ProtocolMapperModel protocolMapperModel,” +
      “KeycloakSession keycloakSession, UserSessionModel userSessionModel, ClientSessionContext clientSessionContext)”;
      log.trace( "{} ENTRY ", METHODNAME );

      String attribute1 = (String) keycloakSession.getAttribute(“attribute1”);

      Map<String, Object> claimMap = accessToken.getOtherClaims();
      claimMap.put(“attribute1”, attribute1);

      log.trace( “{} RETURN {}”, METHODNAME, accessToken);
      return accessToken;
      }

    Override
    protected void setClaim(AccessTokenResponse accessTokenResponse, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession,
    ClientSessionContext clientSessionCtx) {

     String attribute1 = (String) keycloakSession.getAttribute(attribute);
    
     Map<String, Object> claimMap = accessToken.getOtherClaims(); 
     claimMap.put(attribute, attribute1);
     
     OIDCAttributeMapperHelper.mapClaim(accessTokenResponse, mappingModel, claimMap);
    

    }

    /**

    • This method creates protocol mapper that which is required to add the custom claim in the accessToken and IDToken

    • of the Keycloak session

    • param name name of the custom protocol mapper

    • param accessToken

    • param idToken

    • param userInfo

    • return ProtocolMapperModel

    • */
      public static ProtocolMapperModel create(String name,
      boolean accessToken, boolean idToken, boolean userInfo) {
      final String METHODNAME = “create(String name, boolean accessToken, boolean idToken, boolean userInfo)”;
      log.trace( "{} ENTRY ", METHODNAME );

      ProtocolMapperModel mapper = new ProtocolMapperModel();
      mapper.setName(name);
      mapper.setProtocolMapper(TS_PROTOCOL_PROVIDER_ID);
      mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);

      Map<String, String> config = new HashMap();
      config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, “true”);

      mapper.setConfig(config);
      log.trace( “{} RETURN {}”, METHODNAME, mapper);
      return mapper;
      }

    /**

    • This method sets the custom claim in the IDToken of the Keycloak session

    • param token IDToken

    • param mappingModel ProtocolMapperModel

    • param userSession UserSessionModel

    • param keycloakSession KeycloakSession

    • param clientSessionCtx ClientSessionContext
      */
      Override
      protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) {
      final String METHODNAME = “setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx)”;
      log.trace( "{} ENTRY ", METHODNAME);

      super.setClaim(token, mappingModel, userSession, keycloakSession, clientSessionCtx);

      String attribute1 = (String) keycloakSession.getAttribute(attribute);

      Map<String, Object> claimMap = accessToken.getOtherClaims();
      claimMap.put(attribute, attribute1);

      log.trace( "{} RETURN ", METHODNAME);
      }

    /**

    • This method returns the display type of the custom protocol mapper

    • return Token Mapper Category
      */
      Override
      public String getDisplayCategory() {
      final String METHODNAME = “getDisplayCategory()”;
      log.trace( "{} ENTRY ", METHODNAME );

      log.trace( “{} RETURN {}”, METHODNAME, TOKEN_MAPPER_CATEGORY);
      return TOKEN_MAPPER_CATEGORY;
      }

    /**

    • This method returns the display type of the custom protocol mapper
    • return display type of the custom protocol mapper
      */
      Override
      public String getDisplayType() {
      final String METHODNAME = “getDisplayType()”;
      log.trace( "{} ENTRY ", METHODNAME );
      log.trace( “{} RETURN {}”, METHODNAME, CUSTOM_CLAIM);
      return CUSTOM_CLAIM;
      }

    /**

    • This method returns the help text for the custom protocol mapper
    • return Help text for the custom protocol mapper.
      */
      Override
      public String getHelpText() {
      final String METHODNAME = “getHelpText()”;
      log.trace( "{} ENTRY ", METHODNAME );
      log.trace( “{} RETURN {}”, METHODNAME, HELP_TEXT);
      return HELP_TEXT;
      }

    /**

    • This method returns the configuration properties of the custom protocol mapper

    • return configuration properties of the custom protocol mapper
      */
      Override
      public List getConfigProperties() {
      final String METHODNAME = “getConfigProperties()”;
      log.trace( "{} ENTRY ", METHODNAME);

      log.trace( “{} RETURN {}”, METHODNAME, configProperties);
      return configProperties;
      }

    /**

    • This method returns ID of the custom protocol mapper spi that will be registered with Keycloak.
    • @return ID of the custom protocol mapper
      */
      Override
      public String getId() {
      final String METHODNAME = “getId()”;
      log.trace( “{} ENTRY”, METHODNAME );
      log.trace( “{} RETURN {}”, METHODNAME, TS_PROTOCOL_PROVIDER_ID);
      return TS_PROTOCOL_PROVIDER_ID;
      }
      }

Hi @xgp,
I have added file. Please check once.

I saw it. I have some travel for the next few days, but I will check it when I have time.

1 Like

This issue is resolved. We are overriding getAttributes() method of AbstractUserAdapter class and setting parameter in this method at user level. It is working as expected.