From 8259185c08bac4ba89d5aca234a566b6794366f1 Mon Sep 17 00:00:00 2001 From: Ahmed El amraouiyine Date: Wed, 15 Jul 2026 10:27:54 +0100 Subject: [PATCH 1/2] Fix OIDC ID token exchange token type Signed-off-by: Ahmed El amraouiyine --- .../endpoint/TokenExchangeGrantRequest.java | 6 ++++++ ...2TokenRequestParametersConverterTests.java | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java index f80b2c9df93..e2d451b6791 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java @@ -22,6 +22,7 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -49,6 +50,8 @@ public class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantR private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; + private static final String ID_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:id_token"; + private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; private final OAuth2Token subjectToken; @@ -113,6 +116,9 @@ static MultiValueMap defaultParameters(TokenExchangeGrantRequest } private static String tokenType(OAuth2Token token) { + if (token instanceof OidcIdToken) { + return ID_TOKEN_TYPE_VALUE; + } return (token instanceof Jwt) ? JWT_TOKEN_TYPE_VALUE : ACCESS_TOKEN_TYPE_VALUE; } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java index e82efa286ef..adbd2c03cac 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java @@ -35,6 +35,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.util.MultiValueMap; @@ -51,6 +52,8 @@ public class DefaultOAuth2TokenRequestParametersConverterTests { private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; + private static final String ID_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:id_token"; + private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; private ClientRegistration.Builder clientRegistration; @@ -201,4 +204,20 @@ public void convertWhenGrantRequestIsTokenExchangeThenParametersProvided() { assertThat(parameters.get(OAuth2ParameterNames.ACTOR_TOKEN_TYPE)).containsExactly(JWT_TOKEN_TYPE_VALUE); } + @Test + public void convertWhenGrantRequestIsTokenExchangeAndSubjectTokenIsOidcIdTokenThenSubjectTokenTypeIsIdToken() { + ClientRegistration clientRegistration = this.clientRegistration + .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) + .build(); + OidcIdToken subjectToken = OidcIdToken.withTokenValue("id-token").claim("sub", "user").build(); + TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, subjectToken, null); + // @formatter:off + DefaultOAuth2TokenRequestParametersConverter parametersConverter = + new DefaultOAuth2TokenRequestParametersConverter<>(); + // @formatter:on + MultiValueMap parameters = parametersConverter.convert(grantRequest); + assertThat(parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN)).containsExactly(subjectToken.getTokenValue()); + assertThat(parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).containsExactly(ID_TOKEN_TYPE_VALUE); + } + } From 00f63739d5e5133528e626bf3f80a6d7c9bf59fd Mon Sep 17 00:00:00 2001 From: Ahmed El amraouiyine Date: Thu, 16 Jul 2026 16:13:06 +0100 Subject: [PATCH 2/2] Add OIDC actor token exchange coverage Signed-off-by: Ahmed El amraouiyine --- ...h2TokenRequestParametersConverterTests.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java index adbd2c03cac..152526667cf 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java @@ -220,4 +220,22 @@ public void convertWhenGrantRequestIsTokenExchangeAndSubjectTokenIsOidcIdTokenTh assertThat(parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).containsExactly(ID_TOKEN_TYPE_VALUE); } + @Test + public void convertWhenGrantRequestIsTokenExchangeAndActorTokenIsOidcIdTokenThenActorTokenTypeIsIdToken() { + ClientRegistration clientRegistration = this.clientRegistration + .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) + .build(); + OAuth2Token subjectToken = TestOAuth2AccessTokens.scopes("read", "write"); + OidcIdToken actorToken = OidcIdToken.withTokenValue("id-token").claim("sub", "user").build(); + TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, subjectToken, + actorToken); + // @formatter:off + DefaultOAuth2TokenRequestParametersConverter parametersConverter = + new DefaultOAuth2TokenRequestParametersConverter<>(); + // @formatter:on + MultiValueMap parameters = parametersConverter.convert(grantRequest); + assertThat(parameters.get(OAuth2ParameterNames.ACTOR_TOKEN)).containsExactly(actorToken.getTokenValue()); + assertThat(parameters.get(OAuth2ParameterNames.ACTOR_TOKEN_TYPE)).containsExactly(ID_TOKEN_TYPE_VALUE); + } + }