Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,32 @@ public MergedOptions GetOptions(string? authenticationScheme, out string effecti
_serviceProvider.GetService<IOptionsMonitor<OpenIdConnectOptions>>()?.Get(effectiveAuthenticationScheme);
}

// Get the merged options again after the merging has occurred
mergedOptions = _mergedOptionsMonitor.Get(effectiveAuthenticationScheme);

if (string.IsNullOrEmpty(mergedOptions.Instance))
{
var availableSchemes = _serviceProvider.GetService<IAuthenticationSchemeProvider>()?.GetAllSchemesAsync()?.Result?.Select(a => a.Name);
string msg = string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.ProvidedAuthenticationSchemeIsIncorrect,
authenticationScheme, effectiveAuthenticationScheme, availableSchemes != null ? string.Join(",", availableSchemes) : string.Empty);
throw new InvalidOperationException(msg);
// Check if the issue is that MicrosoftIdentityApplicationOptions are not configured
// vs. an incorrect authentication scheme
var microsoftIdentityApplicationOptions = _serviceProvider.GetService<IOptionsMonitor<MicrosoftIdentityApplicationOptions>>()?.Get(effectiveAuthenticationScheme);
bool isMicrosoftIdentityApplicationOptionsConfigured = microsoftIdentityApplicationOptions != null &&
(!string.IsNullOrEmpty(microsoftIdentityApplicationOptions.Instance) ||
(!string.IsNullOrEmpty(microsoftIdentityApplicationOptions.Authority) && microsoftIdentityApplicationOptions.Authority != "//v2.0") ||
!string.IsNullOrEmpty(microsoftIdentityApplicationOptions.ClientId));

if (!isMicrosoftIdentityApplicationOptionsConfigured)
{
string msg = string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.MicrosoftIdentityApplicationOptionsNotConfigured,
effectiveAuthenticationScheme);
throw new InvalidOperationException(msg);
}
else
{
var availableSchemes = _serviceProvider.GetService<IAuthenticationSchemeProvider>()?.GetAllSchemesAsync()?.Result?.Select(a => a.Name);
string msg = string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.ProvidedAuthenticationSchemeIsIncorrect,
authenticationScheme, effectiveAuthenticationScheme, availableSchemes != null ? string.Join(",", availableSchemes) : string.Empty);
throw new InvalidOperationException(msg);
}
}

DefaultCertificateLoader.UserAssignedManagedIdentityClientId = mergedOptions.UserAssignedManagedIdentityClientId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
Expand Down Expand Up @@ -61,6 +62,33 @@ public MergedOptions GetOptions(string? authenticationScheme, out string effecti
_microsoftIdentityOptionsMonitor.Get(effectiveAuthenticationScheme);
_MicrosoftIdentityApplicationOptionsMonitor.Get(effectiveAuthenticationScheme);

// Get the merged options again after the merging has occurred
mergedOptions = _mergedOptionsMonitor.Get(effectiveAuthenticationScheme);

if (string.IsNullOrEmpty(mergedOptions.Instance))
{
// Check if the issue is that MicrosoftIdentityApplicationOptions are not configured
var microsoftIdentityApplicationOptions = _MicrosoftIdentityApplicationOptionsMonitor.Get(effectiveAuthenticationScheme);

bool isMicrosoftIdentityApplicationOptionsConfigured = microsoftIdentityApplicationOptions != null &&
(!string.IsNullOrEmpty(microsoftIdentityApplicationOptions.Instance) ||
(!string.IsNullOrEmpty(microsoftIdentityApplicationOptions.Authority) && microsoftIdentityApplicationOptions.Authority != "//v2.0") ||
!string.IsNullOrEmpty(microsoftIdentityApplicationOptions.ClientId));

if (!isMicrosoftIdentityApplicationOptionsConfigured)
{
string msg = string.Format(System.Globalization.CultureInfo.InvariantCulture, IDWebErrorMessage.MicrosoftIdentityApplicationOptionsNotConfigured,
effectiveAuthenticationScheme);
throw new InvalidOperationException(msg);
}
else
{
string msg = string.Format(System.Globalization.CultureInfo.InvariantCulture, IDWebErrorMessage.ProvidedAuthenticationSchemeIsIncorrect,
authenticationScheme, effectiveAuthenticationScheme, string.Empty);
throw new InvalidOperationException(msg);
}
}

DefaultCertificateLoader.UserAssignedManagedIdentityClientId = mergedOptions.UserAssignedManagedIdentityClientId;
return mergedOptions;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ internal static class IDWebErrorMessage
public const string MicrosoftIdentityWebChallengeUserException = "IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. " +
"See https://aka.ms/ms-id-web/ca_incremental-consent. ";
public const string ProvidedAuthenticationSchemeIsIncorrect = "IDW10503: Cannot determine the cloud Instance. The provided authentication scheme was '{0}'. Microsoft.Identity.Web inferred '{1}' as the authentication scheme. Available authentication schemes are '{2}'. See https://aka.ms/id-web/authSchemes. ";
public const string MicrosoftIdentityApplicationOptionsNotConfigured = "IDW10503: Cannot determine the cloud Instance because MicrosoftIdentityApplicationOptions are not configured for the authentication scheme '{0}'. Please ensure the MicrosoftIdentityApplicationOptions are properly configured in your application setup. See https://aka.ms/ms-id-web/configuration for details. ";
public const string InvalidAssertion = "IDW10504: Invalid assertion: contains unsupported character(s).";
public const string InvalidSubAssertion = "IDW10505: Invalid sub_assertion: contains unsupported character(s).";

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
const Microsoft.Identity.Web.IDWebErrorMessage.MicrosoftIdentityApplicationOptionsNotConfigured = "IDW10503: Cannot determine the cloud Instance because MicrosoftIdentityApplicationOptions are not configured for the authentication scheme '{0}'. Please ensure the MicrosoftIdentityApplicationOptions are properly configured in your application setup. See https://aka.ms/ms-id-web/configuration for details. " -> string!
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
const Microsoft.Identity.Web.IDWebErrorMessage.MicrosoftIdentityApplicationOptionsNotConfigured = "IDW10503: Cannot determine the cloud Instance because MicrosoftIdentityApplicationOptions are not configured for the authentication scheme '{0}'. Please ensure the MicrosoftIdentityApplicationOptions are properly configured in your application setup. See https://aka.ms/ms-id-web/configuration for details. " -> string!
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
const Microsoft.Identity.Web.IDWebErrorMessage.MicrosoftIdentityApplicationOptionsNotConfigured = "IDW10503: Cannot determine the cloud Instance because MicrosoftIdentityApplicationOptions are not configured for the authentication scheme '{0}'. Please ensure the MicrosoftIdentityApplicationOptions are properly configured in your application setup. See https://aka.ms/ms-id-web/configuration for details. " -> string!
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
const Microsoft.Identity.Web.IDWebErrorMessage.MicrosoftIdentityApplicationOptionsNotConfigured = "IDW10503: Cannot determine the cloud Instance because MicrosoftIdentityApplicationOptions are not configured for the authentication scheme '{0}'. Please ensure the MicrosoftIdentityApplicationOptions are properly configured in your application setup. See https://aka.ms/ms-id-web/configuration for details. " -> string!
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
const Microsoft.Identity.Web.IDWebErrorMessage.MicrosoftIdentityApplicationOptionsNotConfigured = "IDW10503: Cannot determine the cloud Instance because MicrosoftIdentityApplicationOptions are not configured for the authentication scheme '{0}'. Please ensure the MicrosoftIdentityApplicationOptions are properly configured in your application setup. See https://aka.ms/ms-id-web/configuration for details. " -> string!
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web.Hosts;
using Microsoft.Identity.Web.Test.Common;
using Xunit;

namespace Microsoft.Identity.Web.Test
{
[Collection(nameof(TokenAcquirerFactorySingletonProtection))]
public class TokenAcquisitionHostErrorMessagesIntegrationTests
{
[Fact]
public void GetOptions_WhenMicrosoftIdentityApplicationOptionsNotConfigured_ThrowsSpecificError()
{
// Arrange - Create a service collection with minimal setup (no MicrosoftIdentityApplicationOptions configured)
var services = new ServiceCollection();
services.AddSingleton<IMergedOptionsStore>(new MergedOptionsStore());

// Register the options mergers to enable the merging behavior
services.AddSingleton<IPostConfigureOptions<MicrosoftIdentityOptions>, MicrosoftIdentityOptionsMerger>();
services.AddSingleton<IPostConfigureOptions<MicrosoftIdentityApplicationOptions>, MicrosoftIdentityApplicationOptionsMerger>();
services.AddSingleton<IPostConfigureOptions<ConfidentialClientApplicationOptions>, ConfidentialClientApplicationOptionsMerger>();

services.Configure<MicrosoftIdentityOptions>("test", opt => { });
services.Configure<ConfidentialClientApplicationOptions>("test", opt => { });
// Note: We don't configure MicrosoftIdentityApplicationOptions to simulate the error condition

var serviceProvider = services.BuildServiceProvider();

var mergedOptionsMonitor = serviceProvider.GetRequiredService<IMergedOptionsStore>();
var ccaOptionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<ConfidentialClientApplicationOptions>>();
var microsoftIdentityOptionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<MicrosoftIdentityOptions>>();
var microsoftIdentityApplicationOptionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<MicrosoftIdentityApplicationOptions>>();

var host = new DefaultTokenAcquisitionHost(
microsoftIdentityOptionsMonitor,
mergedOptionsMonitor,
ccaOptionsMonitor,
microsoftIdentityApplicationOptionsMonitor);

// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
host.GetOptions("test", out string effectiveScheme));

// Debug output
Console.WriteLine($"Actual error message: {exception.Message}");

// This should now use the new error message
Assert.Contains("MicrosoftIdentityApplicationOptions are not configured", exception.Message, StringComparison.Ordinal);
}

[Fact]
public void GetOptions_WhenMicrosoftIdentityApplicationOptionsConfiguredWithInstance_Succeeds()
{
// Arrange - Create a service collection with properly configured MicrosoftIdentityApplicationOptions
var services = new ServiceCollection();
services.AddSingleton<IMergedOptionsStore>(new MergedOptionsStore());

// Register the options mergers to enable the merging behavior
services.AddSingleton<IPostConfigureOptions<MicrosoftIdentityOptions>, MicrosoftIdentityOptionsMerger>();
services.AddSingleton<IPostConfigureOptions<MicrosoftIdentityApplicationOptions>, MicrosoftIdentityApplicationOptionsMerger>();
services.AddSingleton<IPostConfigureOptions<ConfidentialClientApplicationOptions>, ConfidentialClientApplicationOptionsMerger>();

services.Configure<MicrosoftIdentityOptions>("test", opt => { });
services.Configure<ConfidentialClientApplicationOptions>("test", opt => { });
services.Configure<MicrosoftIdentityApplicationOptions>("test", opt =>
{
opt.Instance = "https://login.microsoftonline.com/";
opt.ClientId = "test-client-id";
});

var serviceProvider = services.BuildServiceProvider();

var mergedOptionsMonitor = serviceProvider.GetRequiredService<IMergedOptionsStore>();
var ccaOptionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<ConfidentialClientApplicationOptions>>();
var microsoftIdentityOptionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<MicrosoftIdentityOptions>>();
var microsoftIdentityApplicationOptionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<MicrosoftIdentityApplicationOptions>>();

var host = new DefaultTokenAcquisitionHost(
microsoftIdentityOptionsMonitor,
mergedOptionsMonitor,
ccaOptionsMonitor,
microsoftIdentityApplicationOptionsMonitor);

// Act
var result = host.GetOptions("test", out string effectiveScheme);

// Assert
Assert.NotNull(result);
Assert.Equal("https://login.microsoftonline.com/", result.Instance);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web.Hosts;
using Microsoft.Identity.Web.Test.Common;
using NSubstitute;
using Xunit;

namespace Microsoft.Identity.Web.Test
{
[Collection(nameof(TokenAcquirerFactorySingletonProtection))]
public class TokenAcquisitionHostErrorMessagesTests
{
[Fact]
public void GetOptions_WhenMicrosoftIdentityApplicationOptionsNotConfigured_ThrowsSpecificError()
{
// Arrange
var mergedOptionsMonitor = Substitute.For<IMergedOptionsStore>();
var ccaOptionsMonitor = Substitute.For<IOptionsMonitor<ConfidentialClientApplicationOptions>>();
var microsoftIdentityOptionsMonitor = Substitute.For<IOptionsMonitor<MicrosoftIdentityOptions>>();
var microsoftIdentityApplicationOptionsMonitor = Substitute.For<IOptionsMonitor<MicrosoftIdentityApplicationOptions>>();

// Configure merged options to have empty Instance (this is the condition that triggers the error)
var emptyMergedOptions = new MergedOptions();
mergedOptionsMonitor.Get(Arg.Any<string>()).Returns(emptyMergedOptions);

// Configure MicrosoftIdentityApplicationOptions to be empty (not configured)
// Note: The default Authority value is "//v2.0" which should be ignored
var emptyAppOptions = new MicrosoftIdentityApplicationOptions();
microsoftIdentityApplicationOptionsMonitor.Get(Arg.Any<string>()).Returns(emptyAppOptions);

var host = new DefaultTokenAcquisitionHost(
microsoftIdentityOptionsMonitor,
mergedOptionsMonitor,
ccaOptionsMonitor,
microsoftIdentityApplicationOptionsMonitor);

// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
host.GetOptions("testScheme", out string effectiveScheme));

Assert.Contains("MicrosoftIdentityApplicationOptions are not configured", exception.Message, StringComparison.Ordinal);
Assert.Contains("testScheme", exception.Message, StringComparison.Ordinal);
}

[Fact]
public void GetOptions_WhenMicrosoftIdentityApplicationOptionsConfiguredButInstanceEmpty_ThrowsSchemeError()
{
// Arrange
var mergedOptionsMonitor = Substitute.For<IMergedOptionsStore>();
var ccaOptionsMonitor = Substitute.For<IOptionsMonitor<ConfidentialClientApplicationOptions>>();
var microsoftIdentityOptionsMonitor = Substitute.For<IOptionsMonitor<MicrosoftIdentityOptions>>();
var microsoftIdentityApplicationOptionsMonitor = Substitute.For<IOptionsMonitor<MicrosoftIdentityApplicationOptions>>();

// Configure merged options to have empty Instance
var emptyMergedOptions = new MergedOptions();
mergedOptionsMonitor.Get(Arg.Any<string>()).Returns(emptyMergedOptions);

// Configure MicrosoftIdentityApplicationOptions to have ClientId configured (indicating it is configured)
var configuredAppOptions = new MicrosoftIdentityApplicationOptions
{
ClientId = "test-client-id"
};
microsoftIdentityApplicationOptionsMonitor.Get(Arg.Any<string>()).Returns(configuredAppOptions);

var host = new DefaultTokenAcquisitionHost(
microsoftIdentityOptionsMonitor,
mergedOptionsMonitor,
ccaOptionsMonitor,
microsoftIdentityApplicationOptionsMonitor);

// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
host.GetOptions("testScheme", out string effectiveScheme));

Assert.Contains("Cannot determine the cloud Instance", exception.Message, StringComparison.Ordinal);
Assert.Contains("authentication scheme", exception.Message, StringComparison.Ordinal);
}

[Fact]
public void GetOptions_WhenMicrosoftIdentityApplicationOptionsHasInstance_DoesNotThrow()
{
// Arrange
var mergedOptionsMonitor = Substitute.For<IMergedOptionsStore>();
var ccaOptionsMonitor = Substitute.For<IOptionsMonitor<ConfidentialClientApplicationOptions>>();
var microsoftIdentityOptionsMonitor = Substitute.For<IOptionsMonitor<MicrosoftIdentityOptions>>();
var microsoftIdentityApplicationOptionsMonitor = Substitute.For<IOptionsMonitor<MicrosoftIdentityApplicationOptions>>();

// Configure merged options to have Instance populated (normal working case)
var configuredMergedOptions = new MergedOptions
{
Instance = "https://login.microsoftonline.com/"
};
mergedOptionsMonitor.Get(Arg.Any<string>()).Returns(configuredMergedOptions);

var host = new DefaultTokenAcquisitionHost(
microsoftIdentityOptionsMonitor,
mergedOptionsMonitor,
ccaOptionsMonitor,
microsoftIdentityApplicationOptionsMonitor);

// Act
var result = host.GetOptions("testScheme", out string effectiveScheme);

// Assert
Assert.NotNull(result);
Assert.Equal("https://login.microsoftonline.com/", result.Instance);
}
}
}