diff --git a/services/src/main/java/org/keycloak/protocol/oidc/DefaultTokenExchangeProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/DefaultTokenExchangeProvider.java index 03d9ca4edf..7bfb925bf8 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/DefaultTokenExchangeProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/DefaultTokenExchangeProvider.java @@ -437,11 +437,15 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider { responseBuilder.getAccessToken().setSessionId(null); } + String issuedTokenType; if (requestedTokenType.equals(OAuth2Constants.REFRESH_TOKEN_TYPE) && OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken() && targetUserSession.getPersistenceState() != UserSessionModel.SessionPersistenceState.TRANSIENT) { responseBuilder.generateRefreshToken(); responseBuilder.getRefreshToken().issuedFor(client.getClientId()); + issuedTokenType = OAuth2Constants.REFRESH_TOKEN_TYPE; + } else { + issuedTokenType = OAuth2Constants.ACCESS_TOKEN_TYPE; } String scopeParam = clientSessionCtx.getClientSession().getNote(OAuth2Constants.SCOPE); @@ -450,6 +454,8 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider { } AccessTokenResponse res = responseBuilder.build(); + res.setOtherClaims(OAuth2Constants.ISSUED_TOKEN_TYPE, issuedTokenType); + event.detail(Details.AUDIENCE, targetClient.getClientId()) .user(targetUser); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java index 2c423f5a2d..67bc8c88ec 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java @@ -304,6 +304,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { { response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret"); + Assert.assertEquals(OAuth2Constants.REFRESH_TOKEN_TYPE, response.getIssuedTokenType()); String exchangedTokenString = response.getAccessToken(); TokenVerifier verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class); AccessToken exchangedToken = verifier.parse().getToken(); @@ -316,7 +317,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { { response = oauth.doTokenExchange(TEST, accessToken, "target", "legal", "secret"); - + Assert.assertEquals(OAuth2Constants.REFRESH_TOKEN_TYPE, response.getIssuedTokenType()); String exchangedTokenString = response.getAccessToken(); TokenVerifier verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class); AccessToken exchangedToken = verifier.parse().getToken(); @@ -332,6 +333,34 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { } } + @Test + public void testExchangeRequestAccessTokenType() throws Exception { + testingClient.server().run(ClientTokenExchangeTest::setupRealm); + + oauth.realm(TEST); + oauth.clientId("client-exchanger"); + OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "user", "password"); + String accessToken = response.getAccessToken(); + TokenVerifier accessTokenVerifier = TokenVerifier.create(accessToken, AccessToken.class); + AccessToken token = accessTokenVerifier.parse().getToken(); + Assert.assertNotNull(token.getSessionId()); + Assert.assertEquals(token.getPreferredUsername(), "user"); + assertTrue(token.getRealmAccess() == null || !token.getRealmAccess().isUserInRole("example")); + + { + response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret", Map.of(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)); + Assert.assertEquals(OAuth2Constants.ACCESS_TOKEN_TYPE, response.getIssuedTokenType()); + String exchangedTokenString = response.getAccessToken(); + TokenVerifier verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class); + AccessToken exchangedToken = verifier.parse().getToken(); + Assert.assertEquals(token.getSessionId(), exchangedToken.getSessionId()); + Assert.assertEquals("client-exchanger", exchangedToken.getIssuedFor()); + Assert.assertEquals("target", exchangedToken.getAudience()[0]); + Assert.assertEquals(exchangedToken.getPreferredUsername(), "user"); + assertTrue(exchangedToken.getRealmAccess().isUserInRole("example")); + } + } + @Test @UncaughtServerErrorExpected public void testExchangeUsingServiceAccount() throws Exception { @@ -347,6 +376,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { { response = oauth.doTokenExchange(TEST, accessToken, "target", "my-service-account", "secret"); + Assert.assertEquals(OAuth2Constants.ACCESS_TOKEN_TYPE, response.getIssuedTokenType()); String exchangedTokenString = response.getAccessToken(); TokenVerifier verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class); AccessToken exchangedToken = verifier.parse().getToken();