Skip to content

Commit 204b92f

Browse files
evgenykotkovonurmicoogullari
authored andcommitted
http.sys: Allow configuring HTTP_AUTH_EX_FLAGs as options (dotnet#51833)
* http.sys: Allow configuring HTTP_AUTH_EX_FLAGs as options The native HTTP.sys API offers two extended authentication flags: - `HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING` and - `HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL` that can be used by users to fine-tune their Windows authentication setups. For instance, the `HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING` flag can be used to avoid having to authenticate every request and make the authentication session-based, thus reducing the overall number of requests and improving the high-latency scenarios. Enabling it can be used to achieve the same behavior as with the `authPersistNonNTLM` option in IIS. (See dotnet#13634) This commit exposes both flags as options in the authentication manager. Because setting the extended flags requires a different property type (`HttpServerExtendedAuthenticationProperty`), we take additional precaution to only use that property type if we actually have the extended flags to set, and use the original `HttpServerAuthenticationProperty` otherwise. * http.sys: Rename: CaptureCredential → CaptureCredentials * http.sys: Clarify description for `EnableKerberosCredentialCaching` * http.sys: Further clarify description for `EnableKerberosCredentialCaching` * http.sys: Further clarify documentation for new options Based on the discussion in dotnet#51990 * http.sys: Tweak documentation for `CaptureCredentials`
1 parent 267094d commit 204b92f

File tree

4 files changed

+103
-4
lines changed

4 files changed

+103
-4
lines changed

src/Servers/HttpSys/src/AuthenticationManager.cs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Runtime.InteropServices;
77
using Microsoft.Extensions.Primitives;
88
using Microsoft.Net.Http.Headers;
9+
using Windows.Win32;
910
using Windows.Win32.Networking.HttpServer;
1011

1112
namespace Microsoft.AspNetCore.Server.HttpSys;
@@ -63,6 +64,24 @@ public bool AllowAnonymous
6364
/// </summary>
6465
public string? AuthenticationDisplayName { get; set; }
6566

67+
/// <summary>
68+
/// If true, the Kerberos authentication credentials are persisted per connection
69+
/// and re-used for subsequent anonymous requests on the same connection.
70+
/// Kerberos or Negotiate authentication must be enabled. The default is false.
71+
/// This option maps to the native HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING flag.
72+
/// <see href="https://learn.microsoft.com/en-us/windows/win32/api/http/ns-http-http_server_authentication_info"/>
73+
/// </summary>
74+
public bool EnableKerberosCredentialCaching { get; set; }
75+
76+
/// <summary>
77+
/// If true, the server captures user credentials from the thread that starts the
78+
/// host and impersonates that user during Kerberos or Negotiate authentication.
79+
/// Kerberos or Negotiate authentication must be enabled. The default is false.
80+
/// This option maps to the native HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL flag.
81+
/// <see href="https://learn.microsoft.com/en-us/windows/win32/api/http/ns-http-http_server_authentication_info"/>
82+
/// </summary>
83+
public bool CaptureCredentials { get; set; }
84+
6685
internal void SetUrlGroupSecurity(UrlGroup urlGroup)
6786
{
6887
Debug.Assert(_urlGroup == null, "SetUrlGroupSecurity called more than once.");
@@ -85,18 +104,39 @@ private unsafe void SetUrlGroupSecurity()
85104
{
86105
authInfo.AuthSchemes = (uint)_authSchemes;
87106

107+
authInfo.ExFlags = 0;
108+
109+
if (EnableKerberosCredentialCaching)
110+
{
111+
authInfo.ExFlags |= (byte)PInvoke.HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING;
112+
}
113+
114+
if (CaptureCredentials)
115+
{
116+
authInfo.ExFlags |= (byte)PInvoke.HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL;
117+
}
118+
88119
// TODO:
89120
// NTLM auth sharing (on by default?) DisableNTLMCredentialCaching
90-
// Kerberos auth sharing (off by default?) HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING
91121
// Mutual Auth - ReceiveMutualAuth
92122
// Digest domain and realm - HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS
93123
// Basic realm - HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS
94124

125+
HTTP_SERVER_PROPERTY property;
126+
if (authInfo.ExFlags != 0)
127+
{
128+
// We need to modify extended fields such as ExFlags, set the extended auth property.
129+
property = HTTP_SERVER_PROPERTY.HttpServerExtendedAuthenticationProperty;
130+
}
131+
else
132+
{
133+
// Otherwise set the regular auth property.
134+
property = HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty;
135+
}
136+
95137
IntPtr infoptr = new IntPtr(&authInfo);
96138

97-
_urlGroup.SetProperty(
98-
HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty,
99-
infoptr, (uint)AuthInfoSize);
139+
_urlGroup.SetProperty(property, infoptr, (uint)AuthInfoSize);
100140
}
101141
}
102142

src/Servers/HttpSys/src/NativeMethods.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
CloseHandle
66
FILE_SKIP_COMPLETION_PORT_ON_SUCCESS
77
FILE_SKIP_SET_EVENT_ON_HANDLE
8+
HTTP_AUTH_EX_FLAG_*
89
HTTP_AUTH_STATUS
910
HTTP_BINDING_INFO
1011
HTTP_CACHE_POLICY
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.CaptureCredentials.get -> bool
3+
Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.CaptureCredentials.set -> void
4+
Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.EnableKerberosCredentialCaching.get -> bool
5+
Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.EnableKerberosCredentialCaching.set -> void

src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,60 @@ public async Task AuthTypes_DisableAutomaticAuthentication(AuthenticationSchemes
400400
}
401401
}
402402

403+
[ConditionalTheory]
404+
[InlineData(AuthenticationSchemes.Negotiate)]
405+
[InlineData(AuthenticationSchemes.NTLM)]
406+
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM)]
407+
public async Task AuthTypes_EnableKerberosCredentialCaching(AuthenticationSchemes authType)
408+
{
409+
using (var server = Utilities.CreateDynamicHost(out var address, options =>
410+
{
411+
options.Authentication.Schemes = authType;
412+
options.Authentication.AllowAnonymous = DenyAnoymous;
413+
options.Authentication.EnableKerberosCredentialCaching = true;
414+
},
415+
httpContext =>
416+
{
417+
// There doesn't seem to be a simple way of testing the `EnableKerberosCredentialCaching`
418+
// setting, but at least check that the server works.
419+
Assert.NotNull(httpContext.User);
420+
Assert.NotNull(httpContext.User.Identity);
421+
Assert.True(httpContext.User.Identity.IsAuthenticated);
422+
return Task.FromResult(0);
423+
}, LoggerFactory))
424+
{
425+
var response = await SendRequestAsync(address, useDefaultCredentials: true);
426+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
427+
}
428+
}
429+
430+
[ConditionalTheory]
431+
[InlineData(AuthenticationSchemes.Negotiate)]
432+
[InlineData(AuthenticationSchemes.NTLM)]
433+
[InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM)]
434+
public async Task AuthTypes_CaptureCredentials(AuthenticationSchemes authType)
435+
{
436+
using (var server = Utilities.CreateDynamicHost(out var address, options =>
437+
{
438+
options.Authentication.Schemes = authType;
439+
options.Authentication.AllowAnonymous = DenyAnoymous;
440+
options.Authentication.CaptureCredentials = true;
441+
},
442+
httpContext =>
443+
{
444+
// There doesn't seem to be a simple way of testing the `CaptureCredentials`
445+
// setting, but at least check that the server works.
446+
Assert.NotNull(httpContext.User);
447+
Assert.NotNull(httpContext.User.Identity);
448+
Assert.True(httpContext.User.Identity.IsAuthenticated);
449+
return Task.FromResult(0);
450+
}, LoggerFactory))
451+
{
452+
var response = await SendRequestAsync(address, useDefaultCredentials: true);
453+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
454+
}
455+
}
456+
403457
private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool useDefaultCredentials = false)
404458
{
405459
HttpClientHandler handler = new HttpClientHandler();

0 commit comments

Comments
 (0)