diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs index 434d2764ce..714a1f02e0 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ManagedIdentityApplicationBuilder.cs @@ -102,6 +102,34 @@ 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(); + + if (Config.ExtraQueryParameters == null) + { + Config.ExtraQueryParameters = extraQueryParameters; + } + else + { + foreach (var kvp in extraQueryParameters) + { + Config.ExtraQueryParameters[kvp.Key] = kvp.Value; // This will overwrite if key exists, or add if new + } + } + + 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..a09e4497a6 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,70 @@ 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); + } + } + + [TestMethod] + public void WithExtraQueryParameters_MultipleCallsMergeValues() + { + var firstParams = new Dictionary + { + { "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"]); + Assert.AreEqual("value2", miBuilder.Config.ExtraQueryParameters["param2"]); + Assert.AreEqual("value3", miBuilder.Config.ExtraQueryParameters["param3"]); + Assert.AreEqual("value4", miBuilder.Config.ExtraQueryParameters["param4"]); + } } }