diff --git a/Directory.Build.props b/Directory.Build.props
index 852b40b56..da15dbaa8 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -91,7 +91,7 @@
4.36.0
4.57.0-preview
3.1.3
- 7.1.0
+ 7.2.0
9.0.0-rc.2.24473.5
9.0.0-rc.2.24474.3
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs b/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs
index aea7d25ca..8f15d5285 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs
@@ -37,6 +37,7 @@ public ConfidentialClientApplicationOptions ConfidentialClientApplicationOptions
// Properties of ConfidentialClientApplication which are not in MicrosoftIdentityOptions
public AadAuthorityAudience AadAuthorityAudience { get; set; }
+ public string? AppHomeTenantId { get; set; }
public AzureCloudInstance AzureCloudInstance { get; set; }
public string? AzureRegion { get; set; }
public IEnumerable? ClientCapabilities { get; set; }
@@ -537,6 +538,8 @@ public static void UpdateMergedOptionsFromMicrosoftIdentityApplicationOptions(Mi
mergedOptions.TenantId = microsoftIdentityApplicationOptions.TenantId;
}
+ mergedOptions.AppHomeTenantId = microsoftIdentityApplicationOptions.AppHomeTenantId;
+
mergedOptions.WithSpaAuthCode |= microsoftIdentityApplicationOptions.WithSpaAuthCode;
if ((mergedOptions.ClientCredentials == null || !mergedOptions.ClientCredentials.Any()) && microsoftIdentityApplicationOptions.ClientCredentials != null)
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt
index 7b9aca83f..74f7de658 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt
@@ -1,3 +1,6 @@
#nullable enable
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor?
+static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt
index 6137a9f55..74f7de658 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt
@@ -1,4 +1,6 @@
#nullable enable
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor?
-
+static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt
index 7b9aca83f..74f7de658 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt
@@ -1,3 +1,6 @@
#nullable enable
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor?
+static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt
index 7b9aca83f..74f7de658 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt
@@ -1,3 +1,6 @@
#nullable enable
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor?
+static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt
index 7b9aca83f..74f7de658 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt
@@ -1,3 +1,6 @@
#nullable enable
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor?
+static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt
index 7b9aca83f..74f7de658 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt
@@ -1,3 +1,6 @@
#nullable enable
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor?
+static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt
index 7b9aca83f..74f7de658 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt
@@ -1,3 +1,6 @@
#nullable enable
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
+Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor?
+static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
index a97db66da..1c7573616 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
@@ -72,7 +72,7 @@ class OAuthConstants
///
/// Meta-tenant identifiers which are not allowed in client credentials.
///
- private readonly HashSet _metaTenantIdentifiers = new HashSet(
+ private static readonly HashSet _metaTenantIdentifiers = new HashSet(
new[]
{
Constants.Common,
@@ -397,6 +397,32 @@ private void LogAuthResult(AuthenticationResult? authenticationResult)
}
}
+ ///
+ /// Resolves the tenant based on if the tenant is already set or the TenantId configured
+ /// in the options or the AppHomeTenantId if the TenantId is a meta tenant.
+ ///
+ /// Provided tenant or null if not provided
+ /// Merged configuration from which to retrieve tenant value as necessary
+ /// Resolved tenant
+ internal static string? ResolveTenant(string? tenant, MergedOptions mergedOptions)
+ {
+ if (string.IsNullOrEmpty(tenant))
+ {
+ tenant = mergedOptions.TenantId;
+ if (!string.IsNullOrEmpty(tenant) && _metaTenantIdentifiers.Contains(tenant!) && !string.IsNullOrEmpty(mergedOptions.AppHomeTenantId))
+ {
+ tenant = mergedOptions.AppHomeTenantId;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(tenant) && _metaTenantIdentifiers.Contains(tenant!))
+ {
+ throw new ArgumentException(IDWebErrorMessage.ClientCredentialTenantShouldBeTenanted, nameof(tenant));
+ }
+
+ return tenant;
+ }
+
///
/// Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user)
/// using either a client credentials or managed identity flow. See https://aka.ms/msal-net-client-credentials for client credentials or
@@ -427,15 +453,7 @@ public async Task GetAuthenticationResultForAppAsync(
MergedOptions mergedOptions = _tokenAcquisitionHost.GetOptions(authenticationScheme ?? tokenAcquisitionOptions?.AuthenticationOptionsName, out _);
- if (string.IsNullOrEmpty(tenant))
- {
- tenant = mergedOptions.TenantId;
- }
-
- if (!string.IsNullOrEmpty(tenant) && _metaTenantIdentifiers.Contains(tenant!))
- {
- throw new ArgumentException(IDWebErrorMessage.ClientCredentialTenantShouldBeTenanted, nameof(tenant));
- }
+ tenant = ResolveTenant(tenant, mergedOptions);
// If using managed identity
if (tokenAcquisitionOptions != null && tokenAcquisitionOptions.ManagedIdentity != null)
diff --git a/tests/Microsoft.Identity.Web.Test/TokenAcquisitionTests.cs b/tests/Microsoft.Identity.Web.Test/TokenAcquisitionTests.cs
new file mode 100644
index 000000000..d375a5f7f
--- /dev/null
+++ b/tests/Microsoft.Identity.Web.Test/TokenAcquisitionTests.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using Xunit;
+
+namespace Microsoft.Identity.Web.Test
+{
+ public class TokenAcquisitionTests
+ {
+ private const string Tenant = "tenant";
+ private const string TenantId = "tenant-id";
+ private const string AppHomeTenantId = "app-home-tenant-id";
+
+ [Theory]
+ [InlineData(null, null, null, null)]
+ [InlineData(null, null, AppHomeTenantId, null)]
+ [InlineData(Tenant, null, null, Tenant)]
+ [InlineData(Tenant, TenantId, null, Tenant)]
+ [InlineData(Tenant, null, AppHomeTenantId, Tenant)]
+ [InlineData(Tenant, TenantId, AppHomeTenantId, Tenant)]
+ [InlineData(null, TenantId, null, TenantId)]
+ [InlineData(null, TenantId, AppHomeTenantId, TenantId)]
+ [InlineData(null, Constants.Common, AppHomeTenantId, AppHomeTenantId)]
+ [InlineData(null, Constants.Organizations, AppHomeTenantId, AppHomeTenantId)]
+ public void TestResolveTenantReturnsCorrectTenant(string? tenant, string? tenantId, string? appHomeTenantId, string? expectedValue)
+ {
+ string? resolvedTenant = TokenAcquisition.ResolveTenant(tenant, new MergedOptions { TenantId = tenantId, AppHomeTenantId = appHomeTenantId });
+ Assert.Equal(expectedValue, resolvedTenant);
+ }
+
+ [Theory]
+ [InlineData(Constants.Common, null)]
+ [InlineData(Constants.Organizations, null)]
+ [InlineData(Constants.Common, TenantId)]
+ [InlineData(Constants.Organizations, TenantId)]
+ [InlineData(Constants.Common, Constants.Common)]
+ [InlineData(Constants.Common, Constants.Organizations)]
+ [InlineData(Constants.Organizations, Constants.Organizations)]
+ [InlineData(Constants.Organizations, Constants.Common)]
+ [InlineData(null, Constants.Common)]
+ [InlineData(null, Constants.Organizations)]
+ public void TestResolveTenantThrowsWhenMetaTenant(string? tenant, string? tenantId)
+ {
+ var exception = Assert.Throws(() => TokenAcquisition.ResolveTenant(tenant, new MergedOptions { TenantId = tenantId }));
+ Assert.StartsWith(IDWebErrorMessage.ClientCredentialTenantShouldBeTenanted, exception.Message, StringComparison.Ordinal);
+ }
+ }
+}