diff --git a/Directory.Build.props b/Directory.Build.props
index 22d9c284d..529106c31 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -69,7 +69,7 @@
4.34.0
4.50.0-preview
3.1.3
- 3.0.1
+ 3.1.0
4.7.2
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs b/src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs
index dbcbfd362..0fd7c6d76 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs
@@ -129,6 +129,7 @@ public static class Constants
internal const string InvalidClient = "invalid_client";
internal const string InvalidKeyError = "AADSTS700027";
internal const string CiamAuthoritySuffix = ".ciamlogin.com";
+ internal const string TestSlice = "dc";
// Blazor challenge URI
internal const string BlazorChallengeUri = "MicrosoftIdentity/Account/Challenge?redirectUri=";
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs b/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs
index 2160ea508..62a5ba03a 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs
@@ -296,6 +296,11 @@ internal static void UpdateMergedOptionsFromMicrosoftIdentityOptions(MicrosoftId
mergedOptions.ClientCredentialsUsingManagedIdentity ??= microsoftIdentityOptions.ClientCredentialsUsingManagedIdentity;
mergedOptions._confidentialClientApplicationOptions = null;
+
+ if ((mergedOptions.ExtraQueryParameters == null || !mergedOptions.ExtraQueryParameters.Any()) && microsoftIdentityOptions.ExtraQueryParameters != null)
+ {
+ mergedOptions.ExtraQueryParameters = microsoftIdentityOptions.ExtraQueryParameters;
+ }
}
internal static void UpdateMergedOptionsFromConfidentialClientApplicationOptions(ConfidentialClientApplicationOptions confidentialClientApplicationOptions, MergedOptions mergedOptions)
@@ -518,6 +523,12 @@ public static void UpdateMergedOptionsFromMicrosoftIdentityApplicationOptions(Mi
{
mergedOptions.TokenDecryptionCredentials = microsoftIdentityApplicationOptions.TokenDecryptionCredentials;
}
+
+ if ((mergedOptions.ExtraQueryParameters == null || !mergedOptions.ExtraQueryParameters.Any()) && microsoftIdentityApplicationOptions.ExtraQueryParameters != null)
+ {
+ mergedOptions.ExtraQueryParameters = microsoftIdentityApplicationOptions.ExtraQueryParameters;
+ }
+
}
private static IEnumerable ComputeFromLegacyClientCredentials(MicrosoftIdentityOptions microsoftIdentityOptions)
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/MicrosoftIdentityOptions.cs b/src/Microsoft.Identity.Web.TokenAcquisition/MicrosoftIdentityOptions.cs
index 07fdfc115..a0e68d2cc 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/MicrosoftIdentityOptions.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/MicrosoftIdentityOptions.cs
@@ -208,5 +208,10 @@ internal bool HasClientCredentials
/// which is the value used by Microsoft.Identity.Web.UI.
///
public PathString? ErrorPath { get; set; } = Constants.ErrorPath;
+
+ ///
+ /// Sets query parameters for the query string in the HTTP request to the IdP.
+ ///
+ public IDictionary? ExtraQueryParameters { get; set; }
}
}
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
index 9e576bd27..c812b6482 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
@@ -133,6 +133,11 @@ public async Task AddAccountToCacheFromAuthorizationCodeAsyn
.WithCcsRoutingHint(backUpAuthRoutingHint)
.WithSpaAuthorizationCode(mergedOptions.WithSpaAuthCode);
+ if (mergedOptions.ExtraQueryParameters != null)
+ {
+ builder.WithExtraQueryParameters((Dictionary)mergedOptions.ExtraQueryParameters);
+ }
+
if (!string.IsNullOrEmpty(authCodeRedemptionParameters.Tenant))
{
builder.WithTenantId(authCodeRedemptionParameters.Tenant);
@@ -352,9 +357,11 @@ public Task GetAuthenticationResultForAppAsync(
if (tokenAcquisitionOptions != null)
{
- if (tokenAcquisitionOptions.ExtraQueryParameters != null)
+ var dict = MergeExtraQueryParameters(mergedOptions, tokenAcquisitionOptions);
+
+ if (dict != null)
{
- builder.WithExtraQueryParameters(new Dictionary(tokenAcquisitionOptions.ExtraQueryParameters));
+ builder.WithExtraQueryParameters(dict);
}
if (tokenAcquisitionOptions.ExtraHeadersParameters != null)
{
@@ -597,7 +604,7 @@ private IConfidentialClientApplication BuildConfidentialClientApplication(Merged
{
builder.WithClientCredentials(
mergedOptions.ClientCredentials!,
- _logger,
+ _logger,
_credentialsLoader,
new CredentialSourceLoaderParameters(mergedOptions.ClientId!, authority));
}
@@ -707,9 +714,11 @@ private IConfidentialClientApplication BuildConfidentialClientApplication(Merged
}
if (tokenAcquisitionOptions != null)
{
- if (tokenAcquisitionOptions.ExtraQueryParameters != null)
+ var dict = MergeExtraQueryParameters(mergedOptions, tokenAcquisitionOptions);
+
+ if (dict != null)
{
- builder.WithExtraQueryParameters(new Dictionary(tokenAcquisitionOptions.ExtraQueryParameters));
+ builder.WithExtraQueryParameters(dict);
}
if (tokenAcquisitionOptions.ExtraHeadersParameters != null)
{
@@ -842,9 +851,11 @@ private Task GetAuthenticationResultForWebAppWithAccountFr
if (tokenAcquisitionOptions != null)
{
- if (tokenAcquisitionOptions.ExtraQueryParameters != null)
+ var dict = MergeExtraQueryParameters(mergedOptions, tokenAcquisitionOptions);
+
+ if (dict != null)
{
- builder.WithExtraQueryParameters(new Dictionary(tokenAcquisitionOptions.ExtraQueryParameters));
+ builder.WithExtraQueryParameters(dict);
}
if (tokenAcquisitionOptions.ExtraHeadersParameters != null)
{
@@ -884,6 +895,27 @@ private Task GetAuthenticationResultForWebAppWithAccountFr
return builder.ExecuteAsync(tokenAcquisitionOptions != null ? tokenAcquisitionOptions.CancellationToken : CancellationToken.None);
}
+ internal static Dictionary? MergeExtraQueryParameters(
+ MergedOptions mergedOptions,
+ TokenAcquisitionOptions tokenAcquisitionOptions)
+ {
+ if (tokenAcquisitionOptions.ExtraQueryParameters != null)
+ {
+ var mergedDict = new Dictionary(tokenAcquisitionOptions.ExtraQueryParameters);
+ if (mergedOptions.ExtraQueryParameters != null)
+ {
+ foreach (var pair in mergedOptions!.ExtraQueryParameters)
+ {
+ if (!mergedDict!.ContainsKey(pair.Key))
+ mergedDict.Add(pair.Key, pair.Value);
+ }
+ }
+ return mergedDict;
+ }
+
+ return (Dictionary?)mergedOptions.ExtraQueryParameters;
+ }
+
protected static bool AcceptedTokenVersionMismatch(MsalUiRequiredException msalServiceException)
{
// Normally app developers should not make decisions based on the internal AAD code
diff --git a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs
index 1501dca57..4c65e7f3e 100644
--- a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs
+++ b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs
@@ -168,6 +168,10 @@ private static void AddMicrosoftIdentityWebApiImplementation(
if (mergedOptions.Authority != null)
{
mergedOptions.Authority = AuthorityHelpers.BuildCiamAuthorityIfNeeded(mergedOptions.Authority);
+ if (mergedOptions.ExtraQueryParameters != null)
+ {
+ options.MetadataAddress = mergedOptions.Authority + "/.well-known/openid-configuration?" + string.Join("&", mergedOptions.ExtraQueryParameters.Select(p => $"{p.Key}={p.Value}"));
+ }
}
MergedOptionsValidation.Validate(mergedOptions);
diff --git a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderExtensions.cs b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderExtensions.cs
index 273b7f353..f3932d70a 100644
--- a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderExtensions.cs
+++ b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderExtensions.cs
@@ -226,7 +226,7 @@ private static void AddMicrosoftIdentityWebAppInternal(
{
builder.Services.AddSingleton, MicrosoftIdentityOptionsMerger>();
}
-
+
builder.Services.Configure(openIdConnectScheme, configureMicrosoftIdentityOptions);
builder.Services.AddSingleton();
builder.Services.AddHttpClient();
@@ -293,6 +293,10 @@ private static void AddMicrosoftIdentityWebAppInternal(
if (mergedOptions.Authority != null)
{
mergedOptions.Authority = AuthorityHelpers.BuildCiamAuthorityIfNeeded(mergedOptions.Authority);
+ if (mergedOptions.ExtraQueryParameters != null)
+ {
+ options.MetadataAddress = mergedOptions.Authority + "/.well-known/openid-configuration?" + string.Join("&", mergedOptions.ExtraQueryParameters.Select(p => $"{p.Key}={p.Value}"));
+ }
}
PopulateOpenIdOptionsFromMergedOptions(options, mergedOptions);
@@ -375,6 +379,14 @@ private static void AddMicrosoftIdentityWebAppInternal(
additionClaims);
}
+ if (mergedOptions.ExtraQueryParameters != null)
+ {
+ foreach (var ExtraQP in mergedOptions.ExtraQueryParameters)
+ {
+ context.ProtocolMessage.SetParameter(ExtraQP.Key, ExtraQP.Value);
+ }
+ }
+
if (mergedOptions.IsB2C)
{
// When a new Challenge is returned using any B2C user flow different than susi, we must change
diff --git a/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAuthorityTests.cs b/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAuthorityTests.cs
index 16f5ae260..c41c95a29 100644
--- a/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAuthorityTests.cs
+++ b/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAuthorityTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using Microsoft.AspNetCore.Authentication;
@@ -231,5 +232,82 @@ public void TestParseAuthorityIfNecessary()
Assert.Equal(TestConstants.AadInstance, mergedOptions.Instance);
Assert.Equal(TestConstants.TenantIdAsGuid, mergedOptions.TenantId);
}
+
+ [Fact]
+ public void MergeExtraQueryParametersTest()
+ {
+ // Arrange
+ var mergedOptions = new MergedOptions
+ {
+ ExtraQueryParameters = new Dictionary
+ {
+ { "key1", "value1" },
+ { "key2", "value2" }
+ }
+ };
+ var tokenAcquisitionOptions = new TokenAcquisitionOptions
+ {
+ ExtraQueryParameters = new Dictionary
+ {
+ { "key1", "newvalue1" },
+ { "key3", "value3" }
+ }
+ };
+
+ // Act
+ var mergedDict = TokenAcquisition.MergeExtraQueryParameters(mergedOptions, tokenAcquisitionOptions);
+
+
+ // Assert
+ Assert.Equal(3, mergedDict!.Count);
+ Assert.Equal("newvalue1", mergedDict["key1"]);
+ Assert.Equal("value2", mergedDict["key2"]);
+ Assert.Equal("value3", mergedDict["key3"]);
+ }
+
+ [Fact]
+ public void MergeExtraQueryParameters_TokenAcquisitionOptionsNull_Test()
+ {
+ // Arrange
+ var mergedOptions = new MergedOptions
+ {
+ ExtraQueryParameters = new Dictionary
+ {
+ { "key1", "value1" },
+ { "key2", "value2" }
+ }
+ };
+ var tokenAcquisitionOptions = new TokenAcquisitionOptions
+ {
+ ExtraQueryParameters = null,
+ };
+
+ // Act
+ var mergedDict = TokenAcquisition.MergeExtraQueryParameters(mergedOptions, tokenAcquisitionOptions);
+
+ // Assert
+ Assert.Equal("value1", mergedDict!["key1"]);
+ Assert.Equal("value2", mergedDict["key2"]);
+ }
+
+ [Fact]
+ public void MergeExtraQueryParameters_MergedOptionsNull_Test()
+ {
+ // Arrange
+ var mergedOptions = new MergedOptions
+ {
+ ExtraQueryParameters = null,
+ };
+ var tokenAcquisitionOptions = new TokenAcquisitionOptions
+ {
+ ExtraQueryParameters = null,
+ };
+
+ // Act
+ var mergedDict = TokenAcquisition.MergeExtraQueryParameters(mergedOptions, tokenAcquisitionOptions);
+
+ // Assert
+ Assert.Null(mergedDict);
+ }
}
}