diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp index a849e3df95d9..abc20ef05ecf 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp @@ -516,7 +516,13 @@ http_get_authentication_information( ) { *pstrAuthType = SysAllocString(pInProcessHandler->QueryHttpContext()->GetUser()->GetAuthenticationType()); + // prefer GetPrimaryToken over GetImpersonationToken as that's what we've been using since before .NET 10 + // we'll fallback to GetImpersonationToken if GetPrimaryToken is not available *pvToken = pInProcessHandler->QueryHttpContext()->GetUser()->GetPrimaryToken(); + if (*pvToken == nullptr) + { + *pvToken = pInProcessHandler->QueryHttpContext()->GetUser()->GetImpersonationToken(); + } return S_OK; } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp index 7f109cc8fe21..af42c0d6889f 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp @@ -819,11 +819,20 @@ FORWARDING_HANDLER::GetHeaders( (_wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"negotiate") == 0 || _wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"ntlm") == 0)) { - if (m_pW3Context->GetUser()->GetPrimaryToken() != nullptr && - m_pW3Context->GetUser()->GetPrimaryToken() != INVALID_HANDLE_VALUE) + // prefer GetPrimaryToken over GetImpersonationToken as that's what we've been using since before .NET 10 + // we'll fallback to GetImpersonationToken if GetPrimaryToken is not available + HANDLE authToken = m_pW3Context->GetUser()->GetPrimaryToken(); + if (authToken == nullptr || + authToken == INVALID_HANDLE_VALUE) + { + authToken = m_pW3Context->GetUser()->GetImpersonationToken(); + } + + if (authToken != nullptr && + authToken != INVALID_HANDLE_VALUE) { HANDLE hTargetTokenHandle = nullptr; - RETURN_IF_FAILED(pServerProcess->SetWindowsAuthToken(m_pW3Context->GetUser()->GetPrimaryToken(), + RETURN_IF_FAILED(pServerProcess->SetWindowsAuthToken(authToken, &hTargetTokenHandle)); // diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/RequiresIISAttribute.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/RequiresIISAttribute.cs index 7c2e50e594e2..9bb25c50f990 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/RequiresIISAttribute.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/RequiresIISAttribute.cs @@ -136,7 +136,7 @@ public RequiresIISAttribute(IISCapability capabilities) IsMet &= available; if (!available) { - SkipReason += $"The machine does have {module.Capability} available."; + SkipReason += $"The machine does not have {module.Capability} available."; } } } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs index 4de20bac69c3..28d2ff05ed3b 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Net; using System.Net.Http; +using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; @@ -58,4 +60,51 @@ public async Task WindowsAuthTest(TestVariant variant) Assert.StartsWith("Windows:", responseText); Assert.Contains(Environment.UserName, responseText); } + + [ConditionalTheory] + [RequiresIIS(IISCapability.WindowsAuthentication)] + [MemberData(nameof(TestVariants))] + public async Task WindowsAuthWithImpersonationLevelTest(TestVariant variant) + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(variant); + deploymentParameters.SetAnonymousAuth(enabled: false); + deploymentParameters.SetWindowsAuth(); + + // The default in hosting sets windows auth to true. + var deploymentResult = await DeployAsync(deploymentParameters); + + var impersonationLevels = new TokenImpersonationLevel[] + { + TokenImpersonationLevel.None, + TokenImpersonationLevel.Identification, + TokenImpersonationLevel.Impersonation, + TokenImpersonationLevel.Delegation, + TokenImpersonationLevel.Anonymous + }; + + foreach (var impersonationLevel in impersonationLevels) + { + // TokenImpersonationLevel is not supported by HttpClient so we need to use HttpWebRequest to test it. +#pragma warning disable SYSLIB0014 // Type or member is obsolete + var request = HttpWebRequest.CreateHttp($"{deploymentResult.HttpClient.BaseAddress}Auth"); +#pragma warning restore SYSLIB0014 // Type or member is obsolete + request.ImpersonationLevel = impersonationLevel; + request.Method = "GET"; + request.UseDefaultCredentials = true; + + using var response = request.GetResponse(); + using var reader = new StreamReader(response.GetResponseStream()); + var responseText = await reader.ReadToEndAsync(); + + try + { + Assert.StartsWith("Windows:", responseText); + Assert.Contains(Environment.UserName, responseText); + } + catch (Exception ex) + { + Assert.Fail($"'TokenImpersonationLevel.{impersonationLevel}' failed with: {ex.Message}"); + } + } + } }