diff --git a/core/src/main/java/org/keycloak/representations/IDToken.java b/core/src/main/java/org/keycloak/representations/IDToken.java index 68e5290d89..858ef7a64d 100755 --- a/core/src/main/java/org/keycloak/representations/IDToken.java +++ b/core/src/main/java/org/keycloak/representations/IDToken.java @@ -17,7 +17,6 @@ package org.keycloak.representations; -import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import org.keycloak.TokenCategory; @@ -66,10 +65,8 @@ public class IDToken extends JsonWebToken { protected Long auth_time; - // session_state is deprecated, sid should be used instead - @JsonProperty(SESSION_STATE) - @JsonAlias(SESSION_ID) - protected String sessionState; + @JsonProperty(SESSION_ID) + protected String sessionId; @JsonProperty(AT_HASH) protected String accessTokenHash; @@ -177,17 +174,22 @@ public class IDToken extends JsonWebToken { this.auth_time = Long.valueOf(authTime); } - @JsonProperty(SESSION_ID) + public String getSessionId() { - return sessionState; + return sessionId; } + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + /** + * @deprecated Use {@link #getSessionId()} instead. + */ + @Deprecated + @JsonIgnore public String getSessionState() { - return sessionState; - } - - public void setSessionState(String sessionState) { - this.sessionState = sessionState; + return sessionId; } public String getAccessTokenHash() { diff --git a/core/src/main/java/org/keycloak/representations/RefreshToken.java b/core/src/main/java/org/keycloak/representations/RefreshToken.java index 497e806ffc..b7c0f77b13 100755 --- a/core/src/main/java/org/keycloak/representations/RefreshToken.java +++ b/core/src/main/java/org/keycloak/representations/RefreshToken.java @@ -43,7 +43,7 @@ public class RefreshToken extends AccessToken { this.issuer = token.issuer; this.subject = token.subject; this.issuedFor = token.issuedFor; - this.sessionState = token.sessionState; + this.sessionId = token.sessionId; this.nonce = token.nonce; this.audience = new String[] { token.issuer }; this.scope = token.scope; diff --git a/docs/documentation/server_admin/topics/clients/con-protocol-mappers.adoc b/docs/documentation/server_admin/topics/clients/con-protocol-mappers.adoc index 1c4a857dcd..7e8d02bb9b 100644 --- a/docs/documentation/server_admin/topics/clients/con-protocol-mappers.adoc +++ b/docs/documentation/server_admin/topics/clients/con-protocol-mappers.adoc @@ -75,7 +75,7 @@ Further, when the resource server acquires the PII removed from the access token Information that cannot be removed from a lightweight access token:: Protocol mappers can controls which information is put onto an access token and the lightweight access token use the protocol mappers. Therefore, the following information cannot be removed from the lightweight access. + - `exp`, `iat`, `jti`, `iss`, `sub`, `typ`, `azp`, `nonce`, `session_state`, `sid`, `scope`, `cnf` + `exp`, `iat`, `jti`, `iss`, `sub`, `typ`, `azp`, `nonce`, `sid`, `scope`, `cnf` Using a lightweight access token in {project_name}:: By applying `use-lightweight-access-token` executor of <<_client_policies, client policies>> to a client, the client can receive a lightweight access token instead of an access token. The lightweight access token contains a claim controlled by a protocol mapper where its setting `Add to lightweight access token`(default OFF) is turned ON. Also, by turning ON its setting `Add to token introspection` of the protocol mapper, the client can obtain the claim by sending the access token to {project_name}'s token introspection endpoint. diff --git a/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc b/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc index 8f7a92db80..08a6347cae 100644 --- a/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc +++ b/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc @@ -77,4 +77,14 @@ This scope contains preconfigured protocol mappers for the following claims: * `auth_time` -This helps to reduce even more the number of claims in a lightweight access token, but also gives the chance to configure claims that were always added automatically. \ No newline at end of file +This provides additional help to reduce the number of claims in a lightweight access token, but also gives the chance to configure claims that were always added automatically. + += Removed `session_state` claim + +The `session_state` claim, which contains the same value as the `sid` claim, is now removed from all tokens as it is not required according to the OpenID Connect Front-Channel Logout and OpenID Connect Back-Channel Logout specifications. The `session_state` claim remains present in the Access Token Response in accordance with OpenID Connect Session Management specification. + +Note that the `setSessionState()` method is also removed from the `IDToken` class in favor of the `setSessionId()` method, and the `getSessionState()` method is now deprecated. + +A new `Session State (session_state)` mapper is also included and can be assigned to client scopes (for instance `basic` client scope) to revert to the old behavior. + +If an old version of the JS adapter is used, the `Session State (session_state)` mapper should also be used via client scopes as described above. \ No newline at end of file diff --git a/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts b/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts index 31ad607518..8002e6bef0 100644 --- a/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts @@ -301,7 +301,7 @@ describe("Clients test", () => { clientDetailsPage.goToClientScopesEvaluateGeneratedUserInfoTab(); cy.get("div#generatedIdToken").contains('"preferred_username": "admin"'); - cy.get("div#generatedIdToken").contains('"session_state"'); + cy.get("div#generatedIdToken").contains('"sid"'); }); }); diff --git a/js/libs/keycloak-js/src/keycloak.js b/js/libs/keycloak-js/src/keycloak.js index ed10b1fe8d..7c94e0d62e 100755 --- a/js/libs/keycloak-js/src/keycloak.js +++ b/js/libs/keycloak-js/src/keycloak.js @@ -1009,7 +1009,7 @@ function Keycloak (config) { if (token) { kc.token = token; kc.tokenParsed = jwtDecode(token); - kc.sessionId = kc.tokenParsed.session_state; + kc.sessionId = kc.tokenParsed.sid; kc.authenticated = true; kc.subject = kc.tokenParsed.sub; kc.realmAccess = kc.tokenParsed.realm_access; diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java index baa8ce8742..7f914533ac 100644 --- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java +++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java @@ -32,7 +32,6 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; -import java.util.function.Predicate; import java.util.stream.Collectors; import jakarta.ws.rs.HttpMethod; @@ -366,7 +365,7 @@ public class AuthorizationTokenService { if (accessToken.getSessionState() == null) { // Skip generating refresh token for accessToken without sessionState claim. This is "stateless" accessToken not pointing to any real persistent userSession - rpt.setSessionState(null); + rpt.setSessionId(null); } else { if (OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken()) { responseBuilder.generateRefreshToken(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java index 31f03f1009..157006972a 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java @@ -164,7 +164,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi newToken.issuer(token.getIssuer()); newToken.setNonce(token.getNonce()); newToken.setScope(token.getScope()); - newToken.setSessionState(token.getSessionState()); + newToken.setSessionId(token.getSessionId()); // In the case of a refresh token, aud is a basic claim. newToken.audience(token.getAudience()); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index d0fc5d3af9..d7fbb0ad6c 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -991,7 +991,7 @@ public class TokenManager { token.setAcr(acr); } - token.setSessionState(session.getId()); + token.setSessionId(session.getId()); ClientScopeModel offlineAccessScope = KeycloakModelUtils.getClientScopeByName(realm, OAuth2Constants.OFFLINE_ACCESS); boolean offlineTokenRequested = offlineAccessScope == null ? false : clientSessionCtx.getClientScopeIds().contains(offlineAccessScope.getId()); @@ -1190,7 +1190,7 @@ public class TokenManager { idToken.issuedFor(accessToken.getIssuedFor()); idToken.issuer(accessToken.getIssuer()); idToken.setNonce(clientSessionCtx.getAttribute(OIDCLoginProtocol.NONCE_PARAM, String.class)); - idToken.setSessionState(accessToken.getSessionState()); + idToken.setSessionId(accessToken.getSessionId()); idToken.expiration(accessToken.getExpiration()); // Protocol mapper is supposed to set this in case "step_up_authentication" feature enabled diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/ClientCredentialsGrantType.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/ClientCredentialsGrantType.java index 3435a23332..bbd1e67ca7 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/ClientCredentialsGrantType.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/ClientCredentialsGrantType.java @@ -29,7 +29,6 @@ import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventType; import org.keycloak.models.ClientSessionContext; -import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.oidc.OIDCLoginProtocol; @@ -142,7 +141,7 @@ public class ClientCredentialsGrantType extends OAuth2GrantTypeBase { if (useRefreshToken) { responseBuilder = responseBuilder.generateRefreshToken(); } else { - responseBuilder.getAccessToken().setSessionState(null); + responseBuilder.getAccessToken().setSessionId(null); } checkAndBindMtlsHoKToken(responseBuilder, useRefreshToken); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/SessionStateMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/SessionStateMapper.java new file mode 100644 index 0000000000..83421c9d47 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/SessionStateMapper.java @@ -0,0 +1,93 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.protocol.oidc.mappers; + +import org.jboss.logging.Logger; + +import org.keycloak.models.ClientSessionContext; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.representations.IDToken; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Giuseppe Graziano + */ +public class SessionStateMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { + + + public static final String PROVIDER_ID = "oidc-session-state-mapper"; + + private static final Logger logger = Logger.getLogger(AcrProtocolMapper.class); + + private static final List configProperties = new ArrayList<>(); + + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "Session State (session_state)"; + } + + @Override + public String getDisplayCategory() { + return TOKEN_MAPPER_CATEGORY; + } + + @Override + public String getHelpText() { + return "Add Session State (session_state) claim"; + } + + @Override + protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, + ClientSessionContext clientSessionCtx) { + if (userSession != null) { + token.getOtherClaims().put(IDToken.SESSION_STATE, userSession.getId()); + } + } + + public static ProtocolMapperModel create(String name, boolean accessToken, boolean idToken, boolean userInfo, boolean introspectionEndpoint) { + ProtocolMapperModel mapper = new ProtocolMapperModel(); + mapper.setName(name); + mapper.setProtocolMapper(PROVIDER_ID); + mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + Map config = new HashMap<>(); + if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + if (userInfo) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + if (introspectionEndpoint) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION, "true"); + mapper.setConfig(config); + return mapper; + } + +} diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 2a69e65b81..3d7ce31bf5 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -755,7 +755,7 @@ public class AuthenticationManager { token.type(TokenUtil.TOKEN_TYPE_KEYCLOAK_ID); if (session != null) { - token.setSessionState(session.getId()); + token.setSessionId(session.getId()); } if (session != null && session.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0) { diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper index 8f45caae7e..904acd0201 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper +++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper @@ -48,3 +48,4 @@ org.keycloak.protocol.oidc.mappers.ClaimsParameterTokenMapper org.keycloak.protocol.saml.mappers.UserAttributeNameIdMapper org.keycloak.protocol.oidc.mappers.ClaimsParameterWithValueIdTokenMapper org.keycloak.protocol.oidc.mappers.NonceBackwardsCompatibleMapper +org.keycloak.protocol.oidc.mappers.SessionStateMapper diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java index 69bd6d930e..e5cdf7fc0b 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java @@ -210,7 +210,7 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest { JsonNode jsonNode = objectMapper.readTree(tokenResponse); assertTrue(jsonNode.get("active").asBoolean()); - assertEquals(sessionId, jsonNode.get("session_state").asText()); + assertEquals(sessionId, jsonNode.get("sid").asText()); assertEquals("test-app", jsonNode.get("client_id").asText()); assertTrue(jsonNode.has("exp")); assertTrue(jsonNode.has("iat")); @@ -225,7 +225,7 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest { assertTrue(rep.isActive()); assertEquals("test-app", rep.getClientId()); - assertEquals(jsonNode.get("session_state").asText(), rep.getSessionState()); + assertEquals(jsonNode.get("sid").asText(), rep.getSessionState()); assertEquals(jsonNode.get("exp").asInt(), rep.getExpiration()); assertEquals(jsonNode.get("iat").asInt(), rep.getIssuedAt()); assertEquals(jsonNode.get("nbf"), rep.getNbf()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java index b61c6d62f4..92eec32e73 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java @@ -37,7 +37,9 @@ import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.protocol.oidc.mappers.RoleNameMapper; import org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper; import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper; +import org.keycloak.protocol.oidc.mappers.SessionStateMapper; import org.keycloak.representations.AccessToken; +import org.keycloak.representations.IDToken; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -227,12 +229,12 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest(TEST_CLIENT_SECRET); String accessToken = response.getAccessToken(); logger.debug("accessToken:" + accessToken); - assertAccessToken(oauth.verifyToken(accessToken), false, false, true); + assertAccessToken(oauth.verifyToken(accessToken), false, false, false); oauth.clientId(RESOURCE_SERVER_CLIENT_ID); String tokenResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken); logger.debug("tokenResponse:" + tokenResponse); - assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), false); + assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), false, true, false); } finally { deleteProtocolMappers(protocolMappers); } @@ -257,7 +259,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { oauth.clientId(RESOURCE_SERVER_CLIENT_ID); String tokenResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, exchangedTokenString); logger.debug("tokenResponse:" + tokenResponse); - assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), true, true, true); + assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), true, true, false); } finally { deleteProtocolMappers(protocolMappers); } @@ -406,6 +408,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { Assert.assertNotNull(token.getOtherClaims().get("user-session-note")); Assert.assertNotNull(token.getOtherClaims().get("test-claim")); Assert.assertNotNull(token.getOtherClaims().get("group-name")); + Assert.assertNotNull(token.getOtherClaims().get(IDToken.SESSION_STATE)); } Assert.assertNotNull(token.getAudience()); Assert.assertNotNull(token.getAcr()); @@ -424,6 +427,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { Assert.assertNull(token.getOtherClaims().get("user-session-note")); Assert.assertNull(token.getOtherClaims().get("test-claim")); Assert.assertNull(token.getOtherClaims().get("group-name")); + Assert.assertNull(token.getOtherClaims().get(IDToken.SESSION_STATE)); } Assert.assertNull(token.getAcr()); Assert.assertNull(token.getAllowedOrigins()); @@ -439,22 +443,26 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { Assert.assertNotNull(token.getIat()); Assert.assertNotNull(token.getId()); Assert.assertNotNull(token.getType()); + Assert.assertNotNull(token.getIssuedFor()); + Assert.assertNotNull(token.getScope()); + Assert.assertNotNull(token.getIssuer()); + Assert.assertNotNull(token.getSubject()); if (isAuthCodeFlow) { Assert.assertNotNull(token.getSessionId()); } else { Assert.assertNull(token.getSessionId()); } - Assert.assertNotNull(token.getIssuedFor()); - Assert.assertNotNull(token.getScope()); - Assert.assertNotNull(token.getIssuer()); - Assert.assertNotNull(token.getSubject()); } - private void assertBasicClaims(AccessToken token, boolean missing) { + private void assertBasicClaims(AccessToken token, boolean isAuthCodeFlow, boolean missing) { if (missing) { Assert.assertNull(token.getAuth_time()); } else { - Assert.assertNotNull(token.getAuth_time()); + if (isAuthCodeFlow) { + Assert.assertNotNull(token.getAuth_time()); + } else { + Assert.assertNull(token.getAuth_time()); + } } } @@ -468,18 +476,15 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { Assert.assertNull(token.getNonce()); assertMapperClaims(token, isAddToAccessToken, isAuthCodeFlow); assertInitClaims(token, isAuthCodeFlow); - assertBasicClaims(token, missingBasicClaims); + assertBasicClaims(token, isAuthCodeFlow, missingBasicClaims); } - private void assertTokenIntrospectionResponse(AccessToken token, boolean isAuthCodeFlow) { - assertTokenIntrospectionResponse(token, isAuthCodeFlow, true, false); - } - - private void assertTokenIntrospectionResponse(AccessToken token, boolean isAuthCodeFlow, boolean isAddToIntrospect, boolean exchangeToken) { + private void assertTokenIntrospectionResponse(AccessToken token, boolean isAuthCodeFlow, boolean isAddToIntrospect, boolean missingBasicClaims) { Assert.assertNull(token.getNonce()); assertMapperClaims(token, isAddToIntrospect, isAuthCodeFlow); assertInitClaims(token, isAuthCodeFlow); assertIntrospectClaims(token); + assertBasicClaims(token, isAuthCodeFlow, missingBasicClaims); } protected RealmResource testRealm() { @@ -592,6 +597,8 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { }}); protocolMapperList.add(pairwiseSubMapper); } + ProtocolMapperRepresentation sessionStateMapper = createClaimMapper("session-state-mapper", SessionStateMapper.PROVIDER_ID, config); + protocolMapperList.add(sessionStateMapper); } private static ProtocolMapperRepresentation createClaimMapper(String name, String providerId, Map config) { @@ -604,7 +611,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { } private void deleteProtocolMappers(ProtocolMappersResource protocolMappers) { - List mapperNames = new ArrayList<>(Arrays.asList("reference", "audience", "role-name", "group-member", "hardcoded-claim", "hardcoded-role", "user-session-note", "pairwise-sub-mapper")); + List mapperNames = new ArrayList<>(Arrays.asList("reference", "audience", "role-name", "group-member", "hardcoded-claim", "hardcoded-role", "user-session-note", "pairwise-sub-mapper", "session-state-mapper")); List mappers = new ArrayList<>(); for (String mapperName : mapperNames) { mappers.add(ProtocolMapperUtil.getMapperByNameAndProtocol(protocolMappers, OIDCLoginProtocol.LOGIN_PROTOCOL, mapperName));