From 543dc8c4a4554322ded28d8d9365f9f63c857b6c Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Mon, 22 Sep 2025 13:55:02 -0400 Subject: [PATCH 1/3] Added experimental WithExtraQueryParameters to ManagedIdentityApplicationBuilder --- .../ManagedIdentityApplicationBuilder.cs | 17 ++++++++++ .../AbstractManagedIdentity.cs | 4 +++ .../ManagedIdentity/ManagedIdentityRequest.cs | 19 +++++++++++ .../PublicApi/net462/PublicAPI.Unshipped.txt | 1 + .../PublicApi/net472/PublicAPI.Unshipped.txt | 3 +- .../net8.0-android/PublicAPI.Unshipped.txt | 1 + .../net8.0-ios/PublicAPI.Unshipped.txt | 1 + .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 1 + .../netstandard2.0/PublicAPI.Unshipped.txt | 1 + .../Core/Mocks/MockHttpManagerExtensions.cs | 12 ++++++- .../ManagedIdentityTests.cs | 34 +++++++++++++++++++ 11 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs index 434d2764ce..ea0cfb9413 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs @@ -102,6 +102,23 @@ public ManagedIdentityApplicationBuilder WithClientCapabilities(IEnumerable + /// Sets Extra Query Parameters for the query string in the HTTP authentication request. + /// + /// This parameter will be appended as is to the query string in the HTTP authentication request to the authority + /// as a string of segments of the form key=value separated by an ampersand character. + /// The parameter can be null. + /// The builder to chain the .With methods. + /// This API is experimental and it may change in future versions of the library without a major version increment + [EditorBrowsable(EditorBrowsableState.Never)] + public ManagedIdentityApplicationBuilder WithExtraQueryParameters(IDictionary extraQueryParameters) + { + ValidateUseOfExperimentalFeature(); + + Config.ExtraQueryParameters = extraQueryParameters; + return this; + } + /// /// Builds an instance of /// from the parameters set in the . diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs index 405c1b5523..b86d617ac7 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs @@ -70,6 +70,10 @@ public virtual async Task AuthenticateAsync( _requestContext.Logger); } + request.AddExtraQueryParams( + _requestContext.ServiceBundle.Config.ExtraQueryParameters, + _requestContext.Logger); + _requestContext.Logger.Info("[Managed Identity] Sending request to managed identity endpoints."); IRetryPolicy retryPolicy = _requestContext.ServiceBundle.Config.RetryPolicyFactory.GetRetryPolicy(request.RequestType); diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityRequest.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityRequest.cs index 6a7161d2c0..75c6cf4031 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityRequest.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityRequest.cs @@ -72,5 +72,24 @@ internal void AddClaimsAndCapabilities( logger.Info("[Managed Identity] Passing SHA-256 of the 'revoked' token to Managed Identity endpoint."); } } + + /// + /// Adds extra query parameters to the Managed Identity request. + /// + /// Dictionary containing additional query parameters to append to the request. + /// The parameter can be null. + /// Logger instance for recording the operation. + internal void AddExtraQueryParams(IDictionary extraQueryParameters, ILoggerAdapter logger) + { + if (extraQueryParameters != null) + { + foreach (var kvp in extraQueryParameters) + { + QueryParameters[kvp.Key] = kvp.Value; + } + + logger.Info($"[Managed Identity] Adding {extraQueryParameters.Count} extra query parameters to Managed Identity request."); + } + } } } diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index 27d171fb73..d724d3b616 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index ea8ad26bf1..d724d3b616 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource \ No newline at end of file +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index 27d171fb73..d724d3b616 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index 27d171fb73..d724d3b616 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index 27d171fb73..d724d3b616 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index 27d171fb73..d724d3b616 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs index 565ca72e68..c8b63a208d 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs @@ -374,7 +374,8 @@ public static MockHttpMessageHandler AddManagedIdentityMockHandler( HttpStatusCode statusCode = HttpStatusCode.OK, string retryAfterHeader = null, // A number of seconds (e.g., "120"), or an HTTP-date in RFC1123 format (e.g., "Fri, 19 Apr 2025 15:00:00 GMT") bool capabilityEnabled = false, - bool claimsEnabled = false + bool claimsEnabled = false, + IDictionary extraQueryParameters = null ) { HttpResponseMessage responseMessage = new HttpResponseMessage(statusCode) @@ -393,6 +394,15 @@ public static MockHttpMessageHandler AddManagedIdentityMockHandler( capabilityEnabled, claimsEnabled); + // Add extra query parameters if provided + if (extraQueryParameters != null) + { + foreach (var kvp in extraQueryParameters) + { + httpMessageHandler.ExpectedQueryParams[kvp.Key] = kvp.Value; + } + } + if (managedIdentitySourceType == ManagedIdentitySource.MachineLearning) { // For Machine Learning (App Service 2017), the client id param is "clientid" diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs index 0e39a621ae..95e9874510 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; @@ -1508,5 +1509,38 @@ private AbstractManagedIdentity CreateManagedIdentitySource(ManagedIdentitySourc return managedIdentity; } + + [TestMethod] + public async Task ManagedIdentityWithExtraQueryParametersTestAsync() + { + using (new EnvVariableContext()) + using (var httpManager = new MockHttpManager()) + { + SetEnvironmentVariables(ManagedIdentitySource.AppService, AppServiceEndpoint); + + var extraQueryParameters = new Dictionary + { + { "param1", "value1" }, + { "param2", "value2" }, + { "custom_param", "custom_value" } + }; + + var miBuilder = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.SystemAssigned) + .WithExperimentalFeatures(true) + .WithExtraQueryParameters(extraQueryParameters) + .WithHttpManager(httpManager); + + var mi = miBuilder.Build(); + + httpManager.AddManagedIdentityMockHandler( + AppServiceEndpoint, + Resource, + MockHelpers.GetMsiSuccessfulResponse(), + ManagedIdentitySource.AppService, + extraQueryParameters: extraQueryParameters); + + var result = await mi.AcquireTokenForManagedIdentity(Resource).ExecuteAsync().ConfigureAwait(false); + } + } } } From 89154ed4fe50161cf30b39cafae3211b078979df Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Wed, 24 Sep 2025 17:32:05 -0400 Subject: [PATCH 2/3] Implemented feedback --- .../ManagedIdentityApplicationBuilder.cs | 13 +++++++- .../ManagedIdentityTests.cs | 32 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs index ea0cfb9413..714a1f02e0 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs @@ -115,7 +115,18 @@ public ManagedIdentityApplicationBuilder WithExtraQueryParameters(IDictionary + { + { "param1", "value1" }, + { "param2", "value2" } + }; + + var secondParams = new Dictionary + { + { "param3", "value3" }, + { "param4", "value4" }, + { "param1", "newvalue1" } // This should overwrite the first param1 + }; + + var miBuilder = ManagedIdentityApplicationBuilder + .Create(ManagedIdentityId.SystemAssigned) + .WithExperimentalFeatures(true) + .WithExtraQueryParameters(firstParams) + .WithExtraQueryParameters(secondParams); + + // Verify that parameters are merged + Assert.AreEqual(4, miBuilder.Config.ExtraQueryParameters.Count); + + // Verify merged values + Assert.AreEqual("newvalue1", miBuilder.Config.ExtraQueryParameters["param1"]); // Overwritten + Assert.AreEqual("value2", miBuilder.Config.ExtraQueryParameters["param2"]); // From first call + Assert.AreEqual("value3", miBuilder.Config.ExtraQueryParameters["param3"]); // From second call + Assert.AreEqual("value4", miBuilder.Config.ExtraQueryParameters["param4"]); // From second call + } } } From 236c1e24667f478fd4cd638686aae046683cfb5d Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Wed, 24 Sep 2025 17:34:31 -0400 Subject: [PATCH 3/3] removed some comments --- .../ManagedIdentityTests/ManagedIdentityTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs index 65380abf41..a09e4497a6 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs @@ -1569,10 +1569,10 @@ public void WithExtraQueryParameters_MultipleCallsMergeValues() Assert.AreEqual(4, miBuilder.Config.ExtraQueryParameters.Count); // Verify merged values - Assert.AreEqual("newvalue1", miBuilder.Config.ExtraQueryParameters["param1"]); // Overwritten - Assert.AreEqual("value2", miBuilder.Config.ExtraQueryParameters["param2"]); // From first call - Assert.AreEqual("value3", miBuilder.Config.ExtraQueryParameters["param3"]); // From second call - Assert.AreEqual("value4", miBuilder.Config.ExtraQueryParameters["param4"]); // From second call + Assert.AreEqual("newvalue1", miBuilder.Config.ExtraQueryParameters["param1"]); + Assert.AreEqual("value2", miBuilder.Config.ExtraQueryParameters["param2"]); + Assert.AreEqual("value3", miBuilder.Config.ExtraQueryParameters["param3"]); + Assert.AreEqual("value4", miBuilder.Config.ExtraQueryParameters["param4"]); } } }