Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 12, 2026

Problem

When using Azure AD on-behalf-of flow with Spring Security 7.x, the grant_type parameter gets duplicated in token requests, causing AADSTS70003: unsupported_grant_type 'urn:ietf:params:oauth:grant-type:jwt-bearer,urn:ietf:params:oauth:grant-type:jwt-bearer'.

Root cause: AadJwtBearerGrantRequestParametersConverter delegated to DefaultOAuth2TokenRequestParametersConverter, which already adds grant_type. When RestClientJwtBearerTokenResponseClient.addParametersConverter() composes converters via MultiValueMap.addAll(), it appends rather than replaces, creating duplicate entries.

Changes

  • AadJwtBearerGrantRequestParametersConverter: Removed delegation to DefaultOAuth2TokenRequestParametersConverter. Now returns only the Azure-specific requested_token_use=on_behalf_of parameter, letting Spring Security's default converter handle standard OAuth2 parameters.

  • Test coverage: Added assertions verifying the converter adds only the Azure-specific parameter without duplicating standard OAuth2 parameters.

Before:

public MultiValueMap<String, String> convert(JwtBearerGrantRequest request) {
    MultiValueMap<String, String> parameters = delegate.convert(request);  // Already has grant_type
    parameters.add("requested_token_use", "on_behalf_of");  // Adds Azure param
    return parameters;  // grant_type gets duplicated when composed
}

After:

public MultiValueMap<String, String> convert(JwtBearerGrantRequest request) {
    MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
    parameters.add("requested_token_use", "on_behalf_of");  // Only Azure param
    return parameters;  // Standard params handled by Spring Security's default converter
}
Original prompt

This section details on the original issue you should resolve

<issue_title>[BUG] On behalf of token payload is wrong in Spring boot 4.x / spring framework 7.x beta</issue_title>
<issue_description>
Describe the bug

When using the on_behalf_of grant type with Azure AD authentication, the AbstractRestClientOAuth2AccessTokenResponseClient incorrectly merges parameters from multiple converters (DefaultOAuth2TokenRequestParametersConverter and AadJwtBearerGrantRequestParameterConverter). This causes parameter values to be converted into lists instead of single values, resulting in duplicate grant type values being sent to the authorization server.

Exception or Stack Trace

[unsupported_grant_type] AADSTS70003: The app requested an unsupported grant type 'urn:ietf:params:oauth:grant-type:jwt-bearer,urn:ietf:params:oauth:grant-type:jwt-bearer'.

To Reproduce

  1. Configure a Spring Boot application with Azure AD OAuth2 authentication
  2. Set up an authorized client using the on_behalf_of grant type
  3. Attempt to request an access token using the on-behalf-of flow
  4. The request fails with the AADSTS70003 error due to malformed grant type parameter

Code Snippet

The issue occurs in the addParametersConverter() method in AbstractRestClientOAuth2AccessTokenResponseClient.java. When composing converters, the method uses MultiValueMap.addAll() which appends values to existing keys rather than replacing them:

public final void addParametersConverter(Converter<T, MultiValueMap<String, String>> parametersConverter) {
 // ... existing parameters from DefaultOAuth2TokenRequestParametersConverter
 MultiValueMap<String, String> parametersToAdd = parametersConverter.convert(authorizationGrantRequest);
 if (parametersToAdd != null) {
  parameters.addAll(parametersToAdd);  // This creates duplicate list values
 }
 return parameters;
}

Expected behavior

Parameters should be properly merged such that when multiple converters provide the same parameter key, the final value should be a single string value, not a list. The grant type should appear once as urn:ietf:params:oauth:grant-type:jwt-bearer, not duplicated.

Setup (please complete the following information):

  • OS: macOS
  • IDE: IntelliJ IDEA 2025.2.6
  • Library/Libraries: com.azure.spring:spring-cloud-azure-starter:7.0.0-beta.1
  • Java version: 25
  • Frameworks: Spring Boot 4.0.1, Spring Security with OAuth2
  • Build Tool: Maven
    </issue_description>

Comments on the Issue (you are @copilot in this section)

@samvaity @ctNb here is the work in progress PR - https://github.com//pull/47667 that attempts to solve the issue, thanks!

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…f-of flow

Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix incorrect token payload merging in on behalf of grant type Fix OAuth2 JWT Bearer parameter duplication in on-behalf-of flow Jan 12, 2026
Copilot AI requested a review from Netyyyy January 12, 2026 03:04
@Netyyyy
Copy link
Member

Netyyyy commented Jan 13, 2026

@copilot How about keep the delegation, and replace parameters.add() with parameters.set() ?

Copy link
Contributor Author

Copilot AI commented Jan 13, 2026

@copilot How about keep the delegation, and replace parameters.add() with parameters.set() ?

Using .set() instead of .add() wouldn't solve the issue. The problem is that DefaultOAuth2TokenRequestParametersConverter is invoked twice:

  1. Once by RestClientJwtBearerTokenResponseClient (as the default converter)
  2. Again by our converter (via delegation)

When Spring Security composes these converters using MultiValueMap.addAll(), the grant_type from both invocations gets appended, creating [jwt-bearer, jwt-bearer].

The current fix (removing delegation) ensures we only add Azure-specific parameters, while Spring Security's default converter handles the standard OAuth2 parameters once. This prevents the duplication at the composition level.

@github-actions
Copy link
Contributor

API Change Check

APIView identified API level changes in this PR and created the following API reviews

com.azure.spring:spring-cloud-azure-autoconfigure

@Netyyyy Netyyyy marked this pull request as ready for review January 14, 2026 04:47
rujche
rujche previously approved these changes Jan 14, 2026
Copy link
Member

@rujche rujche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Please change target branch to main branch.

@Netyyyy Netyyyy changed the base branch from feature/springcloudazure-7.x to main January 14, 2026 05:06
@Netyyyy Netyyyy dismissed rujche’s stale review January 14, 2026 05:06

The base branch was changed.

@Netyyyy Netyyyy changed the base branch from main to feature/springcloudazure-7.x January 14, 2026 05:07
@Netyyyy
Copy link
Member

Netyyyy commented Jan 14, 2026

LGTM. Please change target branch to main branch.

will use another PR to avoid changes from other files

@Netyyyy Netyyyy merged commit a07df91 into feature/springcloudazure-7.x Jan 14, 2026
33 of 51 checks passed
@Netyyyy Netyyyy deleted the copilot/fix-on-behalf-of-token-bug branch January 14, 2026 05:10
Netyyyy pushed a commit to Netyyyy/azure-sdk-for-java that referenced this pull request Jan 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] On behalf of token payload is wrong in Spring boot 4.x / spring framework 7.x beta

3 participants