diff --git a/Microsoft.AspNetCore.SystemWebAdapters.sln b/Microsoft.AspNetCore.SystemWebAdapters.sln
index 45b045fd5d..f4f0509573 100644
--- a/Microsoft.AspNetCore.SystemWebAdapters.sln
+++ b/Microsoft.AspNetCore.SystemWebAdapters.sln
@@ -100,6 +100,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.System
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SystemWebAdapters.Apis.Tests", "test\Microsoft.AspNetCore.SystemWebAdapters.Apis.Tests\Microsoft.AspNetCore.SystemWebAdapters.Apis.Tests.csproj", "{E4D9A131-DC4E-403F-A10F-65F5E5E42475}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsAuth", "samples\WindowsAuth\WindowsAuth.csproj", "{B5E840F8-2021-4175-BFBF-F9447506242E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -222,6 +224,10 @@ Global
{E4D9A131-DC4E-403F-A10F-65F5E5E42475}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4D9A131-DC4E-403F-A10F-65F5E5E42475}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4D9A131-DC4E-403F-A10F-65F5E5E42475}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B5E840F8-2021-4175-BFBF-F9447506242E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B5E840F8-2021-4175-BFBF-F9447506242E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B5E840F8-2021-4175-BFBF-F9447506242E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B5E840F8-2021-4175-BFBF-F9447506242E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -264,6 +270,7 @@ Global
{FA39AC22-0725-4532-A682-B054ADA5BDA2} = {03E4CEAB-D845-402A-9311-F7390B24BA2A}
{17055F45-E79A-41EF-825E-0B2211433729} = {A1BDA50C-D70B-416C-97F1-74B0649797C5}
{E4D9A131-DC4E-403F-A10F-65F5E5E42475} = {A1BDA50C-D70B-416C-97F1-74B0649797C5}
+ {B5E840F8-2021-4175-BFBF-F9447506242E} = {95915611-30BF-4AFF-AE41-5CDC6F57DCF7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DABA3C65-9D74-4EB6-9B1C-730328710EAD}
diff --git a/samples/WindowsAuth/Program.cs b/samples/WindowsAuth/Program.cs
new file mode 100644
index 0000000000..f2b946c50c
--- /dev/null
+++ b/samples/WindowsAuth/Program.cs
@@ -0,0 +1,23 @@
+using Microsoft.AspNetCore.Authentication.Negotiate;
+using Microsoft.AspNetCore.SystemWebAdapters;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
+ .AddNegotiate();
+
+var app = builder.Build();
+
+app.UseAuthentication();
+
+app.MapGet("/", (HttpContext context) =>
+{
+ return new
+ {
+ User = context.User.Identity?.Name,
+ Principal = context.AsSystemWeb().User.Identity?.Name,
+ LogonUser = OperatingSystem.IsWindows() ? context.AsSystemWeb().Request.LogonUserIdentity?.Name : null,
+ };
+});
+
+app.Run();
diff --git a/samples/WindowsAuth/Properties/launchSettings.json b/samples/WindowsAuth/Properties/launchSettings.json
new file mode 100644
index 0000000000..a80f7522bf
--- /dev/null
+++ b/samples/WindowsAuth/Properties/launchSettings.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": true,
+ "anonymousAuthentication": false,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:50790",
+ "sslPort": 44383
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:5041",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:7245;http://localhost:5041",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/samples/WindowsAuth/WindowsAuth.csproj b/samples/WindowsAuth/WindowsAuth.csproj
new file mode 100644
index 0000000000..8b288ff812
--- /dev/null
+++ b/samples/WindowsAuth/WindowsAuth.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/WindowsAuth/appsettings.Development.json b/samples/WindowsAuth/appsettings.Development.json
new file mode 100644
index 0000000000..0c208ae918
--- /dev/null
+++ b/samples/WindowsAuth/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/samples/WindowsAuth/appsettings.json b/samples/WindowsAuth/appsettings.json
new file mode 100644
index 0000000000..10f68b8c8b
--- /dev/null
+++ b/samples/WindowsAuth/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/Features/IPrincipalUserFeature.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/Features/IRequestUserFeature.cs
similarity index 53%
rename from src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/Features/IPrincipalUserFeature.cs
rename to src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/Features/IRequestUserFeature.cs
index a626a06dc8..20191c197d 100644
--- a/src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/Features/IPrincipalUserFeature.cs
+++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/Features/IRequestUserFeature.cs
@@ -5,6 +5,8 @@
using Microsoft.AspNetCore.Http.Features.Authentication;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
using System.Security.Claims;
using System.Security.Principal;
using System.Web;
@@ -12,13 +14,20 @@
namespace Microsoft.AspNetCore.SystemWebAdapters.Features;
///
-/// Represents the user as an as opposed to the in-built which
-/// expects a .
+/// Represents the users that ASP.NET Framework used.
///
[Experimental(Constants.ExperimentalFeatures.DiagnosticId)]
-public interface IPrincipalUserFeature
+public interface IRequestUserFeature
{
+ ///
+ /// Gets or sets the user that corresponds to
+ ///
IPrincipal? User { get; set; }
+
+ ///
+ /// Gets the logged on user that corresponds to
+ ///
+ WindowsIdentity? LogonUserIdentity { get; }
}
#endif
diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs
index e0de79955d..4c73768a4d 100644
--- a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs
+++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs
@@ -439,6 +439,7 @@ internal HttpRequest() { }
public bool IsLocal { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public bool IsSecureConnection { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public string this[string key] { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
+ public System.Security.Principal.WindowsIdentity LogonUserIdentity { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public System.Collections.Specialized.NameValueCollection Params { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public string Path { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public string PathInfo { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
@@ -483,6 +484,7 @@ public abstract partial class HttpRequestBase
public virtual bool IsLocal { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public virtual bool IsSecureConnection { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public virtual string this[string key] { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
+ public virtual System.Security.Principal.WindowsIdentity LogonUserIdentity { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public virtual System.Collections.Specialized.NameValueCollection Params { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public virtual string Path { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public virtual string PathInfo { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
@@ -527,6 +529,7 @@ public partial class HttpRequestWrapper : System.Web.HttpRequestBase
public override bool IsLocal { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public override bool IsSecureConnection { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public override string this[string key] { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
+ public override System.Security.Principal.WindowsIdentity LogonUserIdentity { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public override System.Collections.Specialized.NameValueCollection Params { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public override string Path { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public override string PathInfo { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs
index 60450ab52b..8a88de30e5 100644
--- a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs
+++ b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs
@@ -99,21 +99,8 @@ public IHttpHandler? Handler
public IPrincipal User
{
- get => Context.Features.Get()?.User ?? Context.User;
- set
- {
- if (Context.Features.Get() is { } feature)
- {
- feature.User = value;
- }
- else
- {
- var newFeature = new PrincipalUserFeature(Context) { User = value };
-
- Context.Features.Set(newFeature);
- Context.Features.Set(newFeature);
- }
- }
+ get => Context.Features.Get()?.User ?? Context.User;
+ set => Context.GetRequestUser().User = value;
}
public HttpSessionState? Session => Context.Features.Get()?.Session;
diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequest.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequest.cs
index 1c8d1b95d1..c00443a7e0 100644
--- a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequest.cs
+++ b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequest.cs
@@ -57,6 +57,8 @@ internal HttpRequest(HttpRequestCore request)
public string CurrentExecutionFilePath => Request.HttpContext.Features.GetRequired().CurrentExecutionFilePath;
+ public WindowsIdentity? LogonUserIdentity => Request.HttpContext.GetRequestUser().LogonUserIdentity;
+
public NameValueCollection Headers => _headers ??= Request.Headers.ToNameValueCollection();
public Uri Url => new(Request.GetEncodedUrl());
diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestBase.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestBase.cs
index a1b124bc5d..b990031ac1 100644
--- a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestBase.cs
+++ b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestBase.cs
@@ -26,6 +26,8 @@ public abstract class HttpRequestBase
public virtual string? FilePath => throw new NotImplementedException();
+ public virtual WindowsIdentity? LogonUserIdentity => throw new NotImplementedException();
+
public virtual ReadEntityBodyMode ReadEntityBodyMode => throw new NotImplementedException();
public virtual void SaveAs(string filename, bool includeHeaders) => throw new NotImplementedException();
diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestWrapper.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestWrapper.cs
index 17cced4ff6..0e852afe0b 100644
--- a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestWrapper.cs
+++ b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequestWrapper.cs
@@ -3,6 +3,7 @@
using System.Collections.Specialized;
using System.IO;
+using System.Security.Principal;
using System.Text;
namespace System.Web
@@ -42,6 +43,8 @@ public override string? ContentType
public override NameValueCollection Form => _request.Form;
+ public override WindowsIdentity? LogonUserIdentity => _request.LogonUserIdentity;
+
public override string HttpMethod => _request.HttpMethod;
public override Stream InputStream => _request.InputStream;
diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Internal/PrincipalUserFeature.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Internal/PrincipalUserFeature.cs
deleted file mode 100644
index fd09dc585a..0000000000
--- a/src/Microsoft.AspNetCore.SystemWebAdapters/Internal/PrincipalUserFeature.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Security.Claims;
-using System.Security.Principal;
-using Microsoft.AspNetCore.Http.Features.Authentication;
-using Microsoft.AspNetCore.SystemWebAdapters.Features;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
-
-namespace Microsoft.AspNetCore.SystemWebAdapters;
-
-internal sealed partial class PrincipalUserFeature : IPrincipalUserFeature, IHttpAuthenticationFeature
-{
- private readonly ILogger _logger;
-
- public PrincipalUserFeature(HttpContextCore context)
- {
- var logger = (ILogger?)context.RequestServices?.GetService()?.CreateLogger();
-
- _logger = logger ?? NullLogger.Instance;
- }
-
- [LoggerMessage(0, LogLevel.Debug, "A custom principal {PrincipalType} is being used and should be replaced with a ClaimsPrincipal derived type.")]
- private partial void LogNonClaimsPrincipal(Type principalType);
-
- public IPrincipal? User { get; set; }
-
- ClaimsPrincipal? IHttpAuthenticationFeature.User
- {
- get => GetOrCreateClaims(User);
- set => User = value;
- }
-
- private ClaimsPrincipal? GetOrCreateClaims(IPrincipal? principal)
- {
- if (principal is null)
- {
- return null;
- }
-
- if (principal is ClaimsPrincipal claimsPrincipal)
- {
- return claimsPrincipal;
- }
-
- LogNonClaimsPrincipal(principal.GetType());
-
- return new ClaimsPrincipal(principal);
- }
-}
diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Internal/RequestUserExtensions.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Internal/RequestUserExtensions.cs
new file mode 100644
index 0000000000..8e6c92a257
--- /dev/null
+++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Internal/RequestUserExtensions.cs
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Security.Claims;
+using System.Security.Principal;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+using Microsoft.AspNetCore.SystemWebAdapters.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace Microsoft.AspNetCore.SystemWebAdapters;
+
+internal static partial class RequestUserExtensions
+{
+ public static IRequestUserFeature GetRequestUser(this HttpContextCore context)
+ {
+ if (context.Features.Get() is { } existing)
+ {
+ return existing;
+ }
+
+ var newFeature = new RequestUserFeature(context) { User = context.User };
+
+ context.Features.Set(newFeature);
+ context.Features.Set(newFeature);
+
+ return newFeature;
+ }
+
+ private sealed partial class RequestUserFeature : IRequestUserFeature, IHttpAuthenticationFeature
+ {
+ private readonly ILogger _logger;
+
+ public RequestUserFeature(HttpContextCore context)
+ {
+ var logger = (ILogger?)context.RequestServices?.GetService()?.CreateLogger();
+
+ _logger = logger ?? NullLogger.Instance;
+
+ User = context.User;
+ }
+
+ [LoggerMessage(0, LogLevel.Debug, "A custom principal {PrincipalType} is being used and should be replaced with a ClaimsPrincipal derived type.")]
+ private partial void LogNonClaimsPrincipal(Type principalType);
+
+ public IPrincipal? User { get; set; }
+
+ WindowsIdentity? IRequestUserFeature.LogonUserIdentity => User?.Identity as WindowsIdentity;
+
+ ClaimsPrincipal? IHttpAuthenticationFeature.User
+ {
+ get => GetOrCreateClaims(User);
+ set => User = value;
+ }
+
+ private ClaimsPrincipal? GetOrCreateClaims(IPrincipal? principal)
+ {
+ if (principal is null)
+ {
+ return null;
+ }
+
+ if (principal is ClaimsPrincipal claimsPrincipal)
+ {
+ return claimsPrincipal;
+ }
+
+ LogNonClaimsPrincipal(principal.GetType());
+
+ return new ClaimsPrincipal(principal);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Microsoft.AspNetCore.SystemWebAdapters.csproj b/src/Microsoft.AspNetCore.SystemWebAdapters/Microsoft.AspNetCore.SystemWebAdapters.csproj
index 2508a38c34..b7d2598197 100644
--- a/src/Microsoft.AspNetCore.SystemWebAdapters/Microsoft.AspNetCore.SystemWebAdapters.csproj
+++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Microsoft.AspNetCore.SystemWebAdapters.csproj
@@ -31,6 +31,8 @@
+
+
diff --git a/test/Microsoft.AspNetCore.SystemWebAdapters.Apis.Tests/VerifyMembersHaveSameSignature.cs b/test/Microsoft.AspNetCore.SystemWebAdapters.Apis.Tests/VerifyMembersHaveSameSignature.cs
index e9fd92cedc..2f7318ec13 100644
--- a/test/Microsoft.AspNetCore.SystemWebAdapters.Apis.Tests/VerifyMembersHaveSameSignature.cs
+++ b/test/Microsoft.AspNetCore.SystemWebAdapters.Apis.Tests/VerifyMembersHaveSameSignature.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -23,8 +24,12 @@ public VerifyMembersHaveSameSignature(ITestOutputHelper output)
[Fact]
public void VerifyMembersOnTypesAreConsistent()
{
- var netstandard = GetDocumentationIds("adapters/netstandard/Microsoft.AspNetCore.SystemWebAdapters.dll", NetStandard20.References.All);
- var framework = GetDocumentationIds("adapters/netfx/Microsoft.AspNetCore.SystemWebAdapters.dll", Net472.References.All);
+ var netstandard = GetDocumentationIds(
+ "adapters/netstandard/Microsoft.AspNetCore.SystemWebAdapters.dll",
+ NetStandard20.References.All.Concat(GetAdditionalNetStandardReferences()));
+ var framework = GetDocumentationIds(
+ "adapters/netfx/Microsoft.AspNetCore.SystemWebAdapters.dll",
+ Net472.References.All);
var ok = File.ReadAllLines("BaselineOk.txt");
netstandard.ExceptWith(framework);
@@ -38,6 +43,12 @@ public void VerifyMembersOnTypesAreConsistent()
Assert.Empty(netstandard);
}
+ private static IEnumerable GetAdditionalNetStandardReferences()
+ {
+ // Even though this isn't the exact reference assembly for .NET Standard, it is sufficient to be able to reconstruct docids
+ yield return ReferenceAssemblies.Net60.Single(p => p.FilePath == "System.Security.Principal.Windows.dll");
+ }
+
public static HashSet GetDocumentationIds(string path, IEnumerable referenceAssemblies)
{
var _adapter = MetadataReference.CreateFromFile(path);