Skip to content

Commit 7489786

Browse files
committed
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 #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.
1 parent c50346d commit 7489786

File tree

4 files changed

+107
-5
lines changed

4 files changed

+107
-5
lines changed

src/Servers/HttpSys/src/AuthenticationManager.cs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ public bool AllowAnonymous
6363
/// </summary>
6464
public string? AuthenticationDisplayName { get; set; }
6565

66+
/// <summary>
67+
/// If true, the Kerberos authentication credentials are cached. Kerberos or Negotiate
68+
/// authentication must be enabled. The default is false.
69+
/// </summary>
70+
public bool EnableKerberosCredentialCaching { get; set; }
71+
72+
/// <summary>
73+
/// If true, the server captures the caller's credentials and uses them for Kerberos
74+
/// or Negotiate authentication. Kerberos or Negotiate authentication must be enabled.
75+
/// The default is false.
76+
/// </summary>
77+
public bool CaptureCredential { get; set; }
78+
6679
internal void SetUrlGroupSecurity(UrlGroup urlGroup)
6780
{
6881
Debug.Assert(_urlGroup == null, "SetUrlGroupSecurity called more than once.");
@@ -87,18 +100,41 @@ private unsafe void SetUrlGroupSecurity()
87100
{
88101
authInfo.AuthSchemes = authSchemes;
89102

103+
authInfo.ExFlags = HttpApiTypes.HTTP_AUTH_EX_FLAGS.NONE;
104+
105+
if (EnableKerberosCredentialCaching)
106+
{
107+
authInfo.ExFlags |=
108+
HttpApiTypes.HTTP_AUTH_EX_FLAGS.HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING;
109+
}
110+
111+
if (CaptureCredential)
112+
{
113+
authInfo.ExFlags |=
114+
HttpApiTypes.HTTP_AUTH_EX_FLAGS.HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL;
115+
}
116+
90117
// TODO:
91118
// NTLM auth sharing (on by default?) DisableNTLMCredentialCaching
92-
// Kerberos auth sharing (off by default?) HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING
93119
// Mutual Auth - ReceiveMutualAuth
94120
// Digest domain and realm - HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS
95121
// Basic realm - HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS
96122

123+
HttpApiTypes.HTTP_SERVER_PROPERTY property;
124+
if (authInfo.ExFlags != HttpApiTypes.HTTP_AUTH_EX_FLAGS.NONE)
125+
{
126+
// We need to modify extended fields such as ExFlags, set the extended auth property.
127+
property = HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerExtendedAuthenticationProperty;
128+
}
129+
else
130+
{
131+
// Otherwise set the regular auth property.
132+
property = HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty;
133+
}
134+
97135
IntPtr infoptr = new IntPtr(&authInfo);
98136

99-
_urlGroup.SetProperty(
100-
HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty,
101-
infoptr, (uint)AuthInfoSize);
137+
_urlGroup.SetProperty(property, infoptr, (uint)AuthInfoSize);
102138
}
103139
}
104140

src/Servers/HttpSys/src/PublicAPI.Unshipped.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ Microsoft.AspNetCore.Server.HttpSys.IHttpSysRequestTimingFeature
3636
Microsoft.AspNetCore.Server.HttpSys.IHttpSysRequestTimingFeature.Timestamps.get -> System.ReadOnlySpan<long>
3737
Microsoft.AspNetCore.Server.HttpSys.IHttpSysRequestTimingFeature.TryGetElapsedTime(Microsoft.AspNetCore.Server.HttpSys.HttpSysRequestTimingType startingTimestampType, Microsoft.AspNetCore.Server.HttpSys.HttpSysRequestTimingType endingTimestampType, out System.TimeSpan elapsed) -> bool
3838
Microsoft.AspNetCore.Server.HttpSys.IHttpSysRequestTimingFeature.TryGetTimestamp(Microsoft.AspNetCore.Server.HttpSys.HttpSysRequestTimingType timestampType, out long timestamp) -> bool
39+
Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.CaptureCredential.get -> bool
40+
Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.CaptureCredential.set -> void
41+
Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager.EnableKerberosCredentialCaching.get -> bool
42+
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_CaptureCredential(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.CaptureCredential = true;
441+
},
442+
httpContext =>
443+
{
444+
// There doesn't seem to be a simple way of testing the `CaptureCredential`
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();

src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ internal struct HTTP_SERVER_AUTHENTICATION_INFO
602602
internal byte ReceiveMutualAuth;
603603
internal byte ReceiveContextHandle;
604604
internal byte DisableNTLMCredentialCaching;
605-
internal byte ExFlags;
605+
internal HTTP_AUTH_EX_FLAGS ExFlags;
606606
HTTP_SERVER_AUTHENTICATION_DIGEST_PARAMS DigestParams;
607607
HTTP_SERVER_AUTHENTICATION_BASIC_PARAMS BasicParams;
608608
}
@@ -699,6 +699,14 @@ internal enum HTTP_AUTH_TYPES : uint
699699
HTTP_AUTH_ENABLE_KERBEROS = 0x00000010,
700700
}
701701

702+
[Flags]
703+
internal enum HTTP_AUTH_EX_FLAGS : byte
704+
{
705+
NONE = 0x00,
706+
HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING = 0x01,
707+
HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL = 0x02,
708+
}
709+
702710
[Flags]
703711
internal enum HTTP_CREATE_REQUEST_QUEUE_FLAG : uint
704712
{

0 commit comments

Comments
 (0)