Adds windows Auth support (#471)

This commit is contained in:
Justin Kotalik 2017-11-10 17:02:31 -08:00 committed by GitHub
parent 9c065bf107
commit 552163ab77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 399 additions and 26 deletions

View File

@ -1,6 +1,6 @@
{
"iisSettings": {
"windowsAuthentication": false,
"windowsAuthentication": true,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5762/",

View File

@ -3,9 +3,11 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.IISIntegration;
namespace NativeIISSample
{
@ -13,7 +15,7 @@ namespace NativeIISSample
{
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthenticationSchemeProvider authSchemeProvider)
{
app.Run(async (context) =>
{
@ -38,8 +40,8 @@ namespace NativeIISSample
await context.Response.WriteAsync(Environment.NewLine);
await context.Response.WriteAsync("User: " + context.User.Identity.Name + Environment.NewLine);
//var scheme = await authSchemeProvider.GetSchemeAsync(IISDefaults.AuthenticationScheme);
//await context.Response.WriteAsync("DisplayName: " + scheme?.DisplayName + Environment.NewLine);
var scheme = await authSchemeProvider.GetSchemeAsync(IISDefaults.AuthenticationScheme);
await context.Response.WriteAsync("DisplayName: " + scheme?.DisplayName + Environment.NewLine);
await context.Response.WriteAsync(Environment.NewLine);

View File

@ -299,12 +299,12 @@
<application name="Active Server Pages" groupId="ASP" />
</applicationDependencies>
<authentication>
<anonymousAuthentication enabled="true" userName="" />
<anonymousAuthentication enabled="false" userName="" />
<basicAuthentication enabled="false" />
<clientCertificateMappingAuthentication enabled="false" />
<digestAuthentication enabled="false" />
<iisClientCertificateMappingAuthentication enabled="false"></iisClientCertificateMappingAuthentication>
<windowsAuthentication enabled="false">
<windowsAuthentication enabled="true">
<providers>
<add value="Negotiate" />
<add value="NTLM" />

View File

@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
public unsafe static extern bool http_set_managed_context(IntPtr pHttpContext, IntPtr pvManagedContext);
[DllImport(AspNetCoreModuleDll)]
public unsafe static extern int http_get_application_paths([MarshalAs(UnmanagedType.BStr)] out string fullPath, [MarshalAs(UnmanagedType.BStr)] out string virtualPath);
public unsafe static extern int http_get_application_properties(ref IISConfigurationData iiConfigData);
[DllImport(AspNetCoreModuleDll)]
public unsafe static extern bool http_shutdown();
@ -98,6 +98,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
[DllImport(AspNetCoreModuleDll)]
internal unsafe static extern int http_response_set_known_header(IntPtr pHttpContext, int headerId, byte* pHeaderValue, ushort length, bool fReplace);
[DllImport(AspNetCoreModuleDll)]
public unsafe static extern int http_get_authentication_information(IntPtr pHttpContext, [MarshalAs(UnmanagedType.BStr)] out string authType, out IntPtr token);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
@ -105,6 +108,5 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{
return GetModuleHandle(AspNetCoreModuleDll) != IntPtr.Zero;
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.IISIntegration
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct IISConfigurationData
{
[MarshalAs(UnmanagedType.BStr)]
public string pwzFullApplicationPath;
[MarshalAs(UnmanagedType.BStr)]
public string pwzVirtualApplicationPath;
public bool fWindowsAuthEnabled;
public bool fBasicAuthEnabled;
public bool fAnonymousAuthEnable;
}
}

View File

@ -7,10 +7,12 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
@ -22,7 +24,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
IHttpUpgradeFeature,
IHttpConnectionFeature,
IHttpRequestLifetimeFeature,
IHttpRequestIdentifierFeature
IHttpRequestIdentifierFeature,
IHttpAuthenticationFeature
{
// NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation,
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
@ -223,6 +226,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
set => TraceIdentifier = value;
}
ClaimsPrincipal IHttpAuthenticationFeature.User
{
get => User;
set => User = value;
}
public IAuthenticationHandler Handler { get; set; }
object IFeatureCollection.this[Type key]
{
get => FastFeatureGet(key);

View File

@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
private static readonly Type IISHttpContextType = typeof(IISHttpContext);
private object _currentIHttpRequestFeature;
private object _currentIHttpResponseFeature;
@ -61,6 +62,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
_currentIHttpMinRequestBodyDataRateFeature = this;
_currentIHttpMinResponseDataRateFeature = this;
_currentIHttpBodyControlFeature = this;
_currentIHttpAuthenticationFeature = this;
}
internal object FastFeatureGet(Type key)
@ -133,6 +135,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{
return _currentIHttpSendFileFeature;
}
if (key == IISHttpContextType)
{
return this;
}
return ExtraFeatureGet(key);
}
@ -224,6 +231,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{
_currentIHttpSendFileFeature = feature;
return;
}
if (key == IISHttpContextType)
{
throw new InvalidOperationException("Cannot set IISHttpContext in feature collection");
};
ExtraFeatureSet(key, feature);
}

View File

@ -10,9 +10,12 @@ using System.IO;
using System.IO.Pipelines;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.AspNetCore.WebUtilities;
@ -57,7 +60,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
private CurrentOperationType _currentOperationType;
private Task _currentOperation = Task.CompletedTask;
internal unsafe IISHttpContext(PipeFactory pipeFactory, IntPtr pHttpContext)
private const string NtlmString = "NTLM";
private const string NegotiateString = "Negotiate";
private const string BasicString = "Basic";
internal unsafe IISHttpContext(PipeFactory pipeFactory, IntPtr pHttpContext, IISOptions options)
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.http_get_raw_request(pHttpContext))
{
_thisHandle = GCHandle.Alloc(this);
@ -120,6 +127,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
HttpResponseHeaders = new HeaderCollection(); // TODO Optimize for known headers
ResponseHeaders = HttpResponseHeaders;
if (options.ForwardWindowsAuthentication)
{
WindowsUser = GetWindowsPrincipal();
if (options.AutomaticAuthentication)
{
User = WindowsUser;
}
}
ResetFeatureCollection();
}
@ -146,7 +162,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
public int LocalPort { get; set; }
public string RequestConnectionId { get; set; }
public string TraceIdentifier { get; set; }
public ClaimsPrincipal User { get; set; }
internal WindowsPrincipal WindowsUser { get; set; }
public Stream RequestBody { get; set; }
public Stream ResponseBody { get; set; }
public IPipe Input { get; set; }
@ -827,7 +844,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
// TODO: dispose managed state (managed objects).
_thisHandle.Free();
}
if (WindowsUser?.Identity is WindowsIdentity wi)
{
wi.Dispose();
}
disposedValue = true;
}
}
@ -851,5 +871,21 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
Write,
Flush
}
private WindowsPrincipal GetWindowsPrincipal()
{
var hr = NativeMethods.http_get_authentication_information(_pHttpContext, out var authenticationType, out var token);
if (hr == 0 && token != IntPtr.Zero && authenticationType != null)
{
if ((authenticationType.Equals(NtlmString, StringComparison.OrdinalIgnoreCase)
|| authenticationType.Equals(NegotiateString, StringComparison.OrdinalIgnoreCase)
|| authenticationType.Equals(BasicString, StringComparison.OrdinalIgnoreCase)))
{
return new WindowsPrincipal(new WindowsIdentity(token, authenticationType));
}
}
return null;
}
}
}

View File

@ -3,9 +3,10 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
using System.Threading;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server;
namespace Microsoft.AspNetCore.Server.IISIntegration
{
@ -13,8 +14,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{
private readonly IHttpApplication<TContext> _application;
public IISHttpContextOfT(PipeFactory pipeFactory, IHttpApplication<TContext> application, IntPtr pHttpContext)
: base(pipeFactory, pHttpContext)
public IISHttpContextOfT(PipeFactory pipeFactory, IHttpApplication<TContext> application, IntPtr pHttpContext, IISOptions options)
: base(pipeFactory, pHttpContext, options)
{
_application = application;
}

View File

@ -6,9 +6,12 @@ using System.IO.Pipelines;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.IISIntegration
{
@ -19,22 +22,30 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
private static NativeMethods.PFN_ASYNC_COMPLETION _onAsyncCompletion = OnAsyncCompletion;
private IISContextFactory _iisContextFactory;
private PipeFactory _pipeFactory = new PipeFactory();
private GCHandle _httpServerHandle;
private IApplicationLifetime _applicationLifetime;
private readonly IApplicationLifetime _applicationLifetime;
private readonly IAuthenticationSchemeProvider _authentication;
private readonly IISOptions _options;
public IFeatureCollection Features { get; } = new FeatureCollection();
public IISHttpServer(IApplicationLifetime applicationLifetime)
public IISHttpServer(IApplicationLifetime applicationLifetime, IAuthenticationSchemeProvider authentication, IOptions<IISOptions> options)
{
_applicationLifetime = applicationLifetime;
_authentication = authentication;
_options = options.Value;
if (_options.ForwardWindowsAuthentication)
{
authentication.AddScheme(new AuthenticationScheme(IISDefaults.AuthenticationScheme, _options.AuthenticationDisplayName, typeof(IISServerAuthenticationHandler)));
}
}
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
_httpServerHandle = GCHandle.Alloc(this);
_iisContextFactory = new IISContextFactory<TContext>(_pipeFactory, application);
_iisContextFactory = new IISContextFactory<TContext>(_pipeFactory, application, _options);
// Start the server by registering the callback
NativeMethods.register_callbacks(_requestHandler, _shutdownHandler, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
@ -115,16 +126,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{
private readonly IHttpApplication<T> _application;
private readonly PipeFactory _pipeFactory;
private readonly IISOptions _options;
public IISContextFactory(PipeFactory pipeFactory, IHttpApplication<T> application)
public IISContextFactory(PipeFactory pipeFactory, IHttpApplication<T> application, IISOptions options)
{
_application = application;
_pipeFactory = pipeFactory;
_options = options;
}
public IISHttpContext CreateHttpContext(IntPtr pHttpContext)
{
return new IISHttpContextOfT<T>(_pipeFactory, _application, pHttpContext);
return new IISHttpContextOfT<T>(_pipeFactory, _application, pHttpContext, _options);
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Server.IISIntegration
{
public class IISServerAuthenticationHandler : IAuthenticationHandler
{
private HttpContext _context;
private IISHttpContext _iisHttpContext;
internal AuthenticationScheme Scheme { get; private set; }
public Task<AuthenticateResult> AuthenticateAsync()
{
var user = _iisHttpContext.WindowsUser;
if (user != null && user.Identity != null && user.Identity.IsAuthenticated)
{
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, Scheme.Name)));
}
else
{
return Task.FromResult(AuthenticateResult.NoResult());
}
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
// We would normally set the www-authenticate header here, but IIS does that for us.
_context.Response.StatusCode = 401;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
_context.Response.StatusCode = 403;
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_iisHttpContext = context.Features.Get<IISHttpContext>();
if (_iisHttpContext == null)
{
throw new InvalidOperationException("No IISHttpContext found.");
}
Scheme = scheme;
_context = context;
return Task.CompletedTask;
}
}
}

View File

@ -46,19 +46,27 @@ namespace Microsoft.AspNetCore.Hosting
hostBuilder.CaptureStartupErrors(true);
// TODO consider adding a configuration load where all variables needed are loaded from ANCM in one call.
var hResult = NativeMethods.http_get_application_paths(out var fullPath, out var virtualPath);
var iisConfigData = new IISConfigurationData();
var hResult = NativeMethods.http_get_application_properties(ref iisConfigData);
var exception = Marshal.GetExceptionForHR(hResult);
if (exception != null)
{
throw exception;
}
hostBuilder.UseContentRoot(fullPath);
hostBuilder.UseContentRoot(iisConfigData.pwzFullApplicationPath);
return hostBuilder.ConfigureServices(services =>
{
services.AddSingleton<IServer, IISHttpServer>();
services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(virtualPath));
services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(iisConfigData.pwzVirtualApplicationPath));
services.AddAuthenticationCore();
services.Configure<IISOptions>(
options =>
{
options.ForwardWindowsAuthentication = iisConfigData.fWindowsAuthEnabled || iisConfigData.fBasicAuthEnabled;
}
);
});
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NET461

View File

@ -0,0 +1,121 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NET461
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace IISIntegration.IISServerFunctionalTests
{
public class AuthenticationTests : LoggedTest
{
public AuthenticationTests(ITestOutputHelper output) : base(output)
{
}
[Fact(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
public Task Authentication_InProcess_IISExpress()
{
return Authentication();
}
private async Task Authentication()
{
var serverType = ServerType.IISExpress;
var architecture = RuntimeArchitecture.x64;
var testName = $"Authentication_{RuntimeFlavor.CoreClr}";
using (StartLog(out var loggerFactory, testName))
{
var logger = loggerFactory.CreateLogger("AuthenticationTest");
var deploymentParameters = new DeploymentParameters(Helpers.GetTestSitesPath(), serverType, RuntimeFlavor.CoreClr, architecture)
{
ApplicationBaseUriHint = $"http://localhost:5051",
EnvironmentName = "Authentication", // Will pick the Start class named 'StartupAuthentication',
ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("Http.config") : null,
SiteName = "HttpTestSite", // This is configured in the Http.config
TargetFramework = "netcoreapp2.0",
ApplicationType = ApplicationType.Portable
};
using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory))
{
var deploymentResult = await deployer.DeployAsync();
var httpClient = deploymentResult.HttpClient;
deploymentResult.HttpClient.Timeout = TimeSpan.FromSeconds(5);
// Request to base address and check if various parts of the body are rendered & measure the cold startup time.
var response = await RetryHelper.RetryRequest(() =>
{
return deploymentResult.HttpClient.GetAsync(string.Empty);
}, logger, deploymentResult.HostShutdownToken, retryCount: 30);
var responseText = await response.Content.ReadAsStringAsync();
try
{
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hello World", responseText);
response = await httpClient.GetAsync("/Anonymous");
responseText = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Anonymous?True", responseText);
response = await httpClient.GetAsync("/Restricted");
responseText = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString());
Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString());
response = await httpClient.GetAsync("/RestrictedNTLM");
responseText = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString());
// Note we can't restrict a challenge to a specific auth type, the native auth modules always add themselves.
Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString());
response = await httpClient.GetAsync("/Forbidden");
responseText = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
var httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true };
httpClient = deploymentResult.CreateHttpClient(httpClientHandler);
response = await httpClient.GetAsync("/Anonymous");
responseText = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Anonymous?True", responseText);
response = await httpClient.GetAsync("/Restricted");
responseText = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotEmpty(responseText);
}
catch (XunitException)
{
logger.LogWarning(response.ToString());
logger.LogWarning(responseText);
throw;
}
}
}
}
}
}
#elif NETCOREAPP2_0
#else
#error Target frameworks need to be updated
#endif

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
{
}
[Fact( Skip ="See https://github.com/aspnet/IISIntegration/issues/424")]
[Fact(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
public Task HelloWorld_InProcess_IISExpress_CoreClr_X64_Portable()
{
return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable);

View File

@ -1026,4 +1026,14 @@
</handlers>
</system.webServer>
</location>
<location path="HttpTestSite">
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
</system.webServer>
</location>
</configuration>

View File

@ -0,0 +1,80 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Principal;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.IISIntegration;
using Microsoft.Extensions.Logging;
using Xunit;
namespace IISTestSite
{
public class StartupAuthentication
{
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
// Simple error page without depending on Diagnostics.
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception ex)
{
if (context.Response.HasStarted)
{
throw;
}
context.Response.Clear();
context.Response.StatusCode = 500;
await context.Response.WriteAsync(ex.ToString());
}
});
app.Use((context, next) =>
{
if (context.Request.Path.Equals("/Anonymous"))
{
return context.Response.WriteAsync("Anonymous?" + !context.User.Identity.IsAuthenticated);
}
if (context.Request.Path.Equals("/Restricted"))
{
if (context.User.Identity.IsAuthenticated)
{
Assert.IsType<WindowsPrincipal>(context.User);
return context.Response.WriteAsync(context.User.Identity.AuthenticationType);
}
else
{
return context.ChallengeAsync(IISDefaults.AuthenticationScheme);
}
}
if (context.Request.Path.Equals("/Forbidden"))
{
return context.ForbidAsync(IISDefaults.AuthenticationScheme);
}
if (context.Request.Path.Equals("/RestrictedNTLM"))
{
if (string.Equals("NTLM", context.User.Identity.AuthenticationType, StringComparison.Ordinal))
{
return context.Response.WriteAsync("NTLM");
}
else
{
return context.ChallengeAsync(IISDefaults.AuthenticationScheme);
}
}
return context.Response.WriteAsync("Hello World");
});
}
}
}