diff --git a/samples/NativeIISSample/Properties/launchSettings.json b/samples/NativeIISSample/Properties/launchSettings.json
index 03665b8024..ca62d6c648 100644
--- a/samples/NativeIISSample/Properties/launchSettings.json
+++ b/samples/NativeIISSample/Properties/launchSettings.json
@@ -1,6 +1,6 @@
{
"iisSettings": {
- "windowsAuthentication": false,
+ "windowsAuthentication": true,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5762/",
diff --git a/samples/NativeIISSample/Startup.cs b/samples/NativeIISSample/Startup.cs
index 85f6a5dd51..ef7bbad8ce 100644
--- a/samples/NativeIISSample/Startup.cs
+++ b/samples/NativeIISSample/Startup.cs
@@ -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);
diff --git a/samples/NativeIISSample/applicationhost.config b/samples/NativeIISSample/applicationhost.config
index b11f212ef2..dd5daa0454 100644
--- a/samples/NativeIISSample/applicationhost.config
+++ b/samples/NativeIISSample/applicationhost.config
@@ -299,12 +299,12 @@
-
+
-
+
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs
index 8015f9c004..4d718793d6 100644
--- a/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs
@@ -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;
}
-
}
}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISConfigurationData.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISConfigurationData.cs
new file mode 100644
index 0000000000..76910ca9d3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISConfigurationData.cs
@@ -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;
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs
index 27962f0c08..2c2c9ef5fe 100644
--- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs
@@ -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);
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs
index 6b534405f5..5ad7590ade 100644
--- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs
@@ -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);
}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs
index a6520425e2..db83adc7cb 100644
--- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs
@@ -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;
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs
index 4fbd3b5956..1701122ec6 100644
--- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs
@@ -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 _application;
- public IISHttpContextOfT(PipeFactory pipeFactory, IHttpApplication application, IntPtr pHttpContext)
- : base(pipeFactory, pHttpContext)
+ public IISHttpContextOfT(PipeFactory pipeFactory, IHttpApplication application, IntPtr pHttpContext, IISOptions options)
+ : base(pipeFactory, pHttpContext, options)
{
_application = application;
}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs
index 9260293613..4417e54c5b 100644
--- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs
@@ -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 options)
{
_applicationLifetime = applicationLifetime;
+ _authentication = authentication;
+ _options = options.Value;
+
+ if (_options.ForwardWindowsAuthentication)
+ {
+ authentication.AddScheme(new AuthenticationScheme(IISDefaults.AuthenticationScheme, _options.AuthenticationDisplayName, typeof(IISServerAuthenticationHandler)));
+ }
}
public Task StartAsync(IHttpApplication application, CancellationToken cancellationToken)
{
_httpServerHandle = GCHandle.Alloc(this);
- _iisContextFactory = new IISContextFactory(_pipeFactory, application);
+ _iisContextFactory = new IISContextFactory(_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 _application;
private readonly PipeFactory _pipeFactory;
+ private readonly IISOptions _options;
- public IISContextFactory(PipeFactory pipeFactory, IHttpApplication application)
+ public IISContextFactory(PipeFactory pipeFactory, IHttpApplication application, IISOptions options)
{
_application = application;
_pipeFactory = pipeFactory;
+ _options = options;
}
public IISHttpContext CreateHttpContext(IntPtr pHttpContext)
{
- return new IISHttpContextOfT(_pipeFactory, _application, pHttpContext);
+ return new IISHttpContextOfT(_pipeFactory, _application, pHttpContext, _options);
}
}
}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISServerAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISServerAuthenticationHandler.cs
new file mode 100644
index 0000000000..eba243f9f3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISServerAuthenticationHandler.cs
@@ -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 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();
+ if (_iisHttpContext == null)
+ {
+ throw new InvalidOperationException("No IISHttpContext found.");
+ }
+
+ Scheme = scheme;
+ _context = context;
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs
index 8a92946ae1..89ecd56739 100644
--- a/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs
+++ b/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs
@@ -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();
- services.AddSingleton(new IISServerSetupFilter(virtualPath));
+ services.AddSingleton(new IISServerSetupFilter(iisConfigData.pwzVirtualApplicationPath));
services.AddAuthenticationCore();
+ services.Configure(
+ options =>
+ {
+ options.ForwardWindowsAuthentication = iisConfigData.fWindowsAuthEnabled || iisConfigData.fBasicAuthEnabled;
+ }
+ );
});
}
diff --git a/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs b/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs
index a61cd3f447..20f2c65c9c 100644
--- a/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs
+++ b/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs
@@ -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
diff --git a/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs b/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs
new file mode 100644
index 0000000000..d069b5211e
--- /dev/null
+++ b/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs
@@ -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
diff --git a/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs b/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs
index 1411934bf9..610ac8c977 100644
--- a/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs
+++ b/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs
@@ -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);
diff --git a/test/IISIntegration.IISServerFunctionalTests/Http.config b/test/IISIntegration.IISServerFunctionalTests/Http.config
index a92444f453..c044b3294c 100644
--- a/test/IISIntegration.IISServerFunctionalTests/Http.config
+++ b/test/IISIntegration.IISServerFunctionalTests/Http.config
@@ -1026,4 +1026,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/test/IISTestSite/StartupAuthentication.cs b/test/IISTestSite/StartupAuthentication.cs
new file mode 100644
index 0000000000..1306332d1f
--- /dev/null
+++ b/test/IISTestSite/StartupAuthentication.cs
@@ -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(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");
+ });
+ }
+ }
+}