From 8ed21d56c81e2ffea3af6ac88f6bba97c286d83d Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Wed, 17 May 2017 13:37:01 -0700 Subject: [PATCH] IIS => Auth 2.0 --- samples/IISSample/Startup.cs | 2 +- .../AuthenticationHandler.cs | 158 +++++++----------- .../IISMiddleware.cs | 93 ++--------- .../IISOptions.cs | 28 +--- ...ft.AspNetCore.Server.IISIntegration.csproj | 1 + .../WebHostBuilderIISExtensions.cs | 4 +- .../NtlmAuthentationTest.cs | 9 +- .../IISMiddlewareTests.cs | 72 ++++++-- ...NetCore.Server.IISIntegration.Tests.csproj | 1 + test/TestSites/StartupNtlmAuthentication.cs | 27 +-- 10 files changed, 163 insertions(+), 232 deletions(-) diff --git a/samples/IISSample/Startup.cs b/samples/IISSample/Startup.cs index 7c1681eb03..b496b8a5cb 100644 --- a/samples/IISSample/Startup.cs +++ b/samples/IISSample/Startup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/AuthenticationHandler.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/AuthenticationHandler.cs index a9ba20762d..ead59b0cb4 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/AuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/AuthenticationHandler.cs @@ -1,138 +1,104 @@ -// 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. using System; -using System.Linq; -using System.Security.Claims; +using System.Globalization; +using System.Security.Principal; using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Authentication; -using Microsoft.AspNetCore.Http.Features.Authentication; using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.IISIntegration { internal class AuthenticationHandler : IAuthenticationHandler { - internal AuthenticationHandler(HttpContext httpContext, IISOptions options, ClaimsPrincipal user) + private const string MSAspNetCoreWinAuthToken = "MS-ASPNETCORE-WINAUTHTOKEN"; + private WindowsPrincipal _user; + private HttpContext _context; + + internal AuthenticationScheme Scheme { get; private set; } + + public Task AuthenticateAsync() { - HttpContext = httpContext; - User = user; - Options = options; + var user = GetUser(); + if (user != null) + { + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, Scheme.Name))); + } + else + { + return Task.FromResult(AuthenticateResult.None()); + } } - internal HttpContext HttpContext { get; } - - internal IISOptions Options { get; } - - internal ClaimsPrincipal User { get; } - - internal IAuthenticationHandler PriorHandler { get; set; } - - public Task AuthenticateAsync(AuthenticateContext context) + private WindowsPrincipal GetUser() { - if (ShouldHandleScheme(context.AuthenticationScheme)) + if (_user == null) { - if (User != null) + var tokenHeader = _context.Request.Headers[MSAspNetCoreWinAuthToken]; + + int hexHandle; + if (!StringValues.IsNullOrEmpty(tokenHeader) + && int.TryParse(tokenHeader, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexHandle)) { - context.Authenticated(User, properties: null, description: null); - } - else - { - context.NotAuthenticated(); + // Always create the identity if the handle exists, we need to dispose it so it does not leak. + var handle = new IntPtr(hexHandle); + var winIdentity = new WindowsIdentity(handle); + + // WindowsIdentity just duplicated the handle so we need to close the original. + NativeMethods.CloseHandle(handle); + + _context.Response.RegisterForDispose(winIdentity); + _user = new WindowsPrincipal(winIdentity); } } - if (PriorHandler != null) - { - return PriorHandler.AuthenticateAsync(context); - } - - return TaskCache.CompletedTask; + return _user; } + public Task ChallengeAsync(ChallengeContext context) { - // Some other provider may have already accepted this challenge. Having multiple providers with - // AutomaticChallenge = true is considered invalid, but changing the default would breaking - // normal Windows auth users. - if (!context.Accepted && ShouldHandleScheme(context.AuthenticationScheme)) + switch (context.Behavior) { - switch (context.Behavior) - { - case ChallengeBehavior.Automatic: - // If there is a principal already, invoke the forbidden code path - if (User == null) - { - goto case ChallengeBehavior.Unauthorized; - } - else - { - goto case ChallengeBehavior.Forbidden; - } - case ChallengeBehavior.Unauthorized: - HttpContext.Response.StatusCode = 401; - // We would normally set the www-authenticate header here, but IIS does that for us. - break; - case ChallengeBehavior.Forbidden: - HttpContext.Response.StatusCode = 403; - break; - } - context.Accept(); + case ChallengeBehavior.Automatic: + // If there is a principal already, invoke the forbidden code path + if (GetUser() == null) + { + goto case ChallengeBehavior.Unauthorized; + } + else + { + goto case ChallengeBehavior.Forbidden; + } + case ChallengeBehavior.Unauthorized: + context.HttpContext.Response.StatusCode = 401; + // We would normally set the www-authenticate header here, but IIS does that for us. + break; + case ChallengeBehavior.Forbidden: + context.HttpContext.Response.StatusCode = 403; + break; } - - if (PriorHandler != null) - { - return PriorHandler.ChallengeAsync(context); - } - return TaskCache.CompletedTask; } - public void GetDescriptions(DescribeSchemesContext context) + public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) { - foreach (var description in Options.AuthenticationDescriptions) - { - context.Accept(description.Items); - } - - if (PriorHandler != null) - { - PriorHandler.GetDescriptions(context); - } + Scheme = scheme; + _context = context; + return TaskCache.CompletedTask; } public Task SignInAsync(SignInContext context) { - // Not supported, fall through - if (PriorHandler != null) - { - return PriorHandler.SignInAsync(context); - } - - return TaskCache.CompletedTask; + throw new NotSupportedException(); } public Task SignOutAsync(SignOutContext context) { - // Not supported, fall through - if (PriorHandler != null) - { - return PriorHandler.SignOutAsync(context); - } - return TaskCache.CompletedTask; } - - private bool ShouldHandleScheme(string authenticationScheme) - { - if (Options.AutomaticAuthentication && string.Equals(AuthenticationManager.AutomaticScheme, authenticationScheme, StringComparison.Ordinal)) - { - return true; - } - - return Options.AuthenticationDescriptions.Any(description => string.Equals(description.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal)); - } } } diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs index b971223db1..c2b06f2705 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs @@ -2,15 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; -using System.Security.Principal; +using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Http.Features.Authentication; -using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; @@ -19,7 +18,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration { public class IISMiddleware { - private const string MSAspNetCoreWinAuthToken = "MS-ASPNETCORE-WINAUTHTOKEN"; + public static readonly string AuthenticationScheme = "Windows"; + private const string MSAspNetCoreClientCert = "MS-ASPNETCORE-CLIENTCERT"; private const string MSAspNetCoreToken = "MS-ASPNETCORE-TOKEN"; @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration private readonly ILogger _logger; private readonly string _pairingToken; - public IISMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions options, string pairingToken) + public IISMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions options, string pairingToken, IAuthenticationSchemeProvider authentication) { if (next == null) { @@ -49,6 +49,13 @@ namespace Microsoft.AspNetCore.Server.IISIntegration _next = next; _options = options.Value; + + + if (_options.ForwardWindowsAuthentication) + { + authentication.AddScheme(new AuthenticationScheme(AuthenticationScheme, displayName: null, handlerType: typeof(AuthenticationHandler))); + } + _pairingToken = pairingToken; _logger = loggerFactory.CreateLogger(); } @@ -80,80 +87,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration if (_options.ForwardWindowsAuthentication) { - var winPrincipal = UpdateUser(httpContext); - var handler = new AuthenticationHandler(httpContext, _options, winPrincipal); - AttachAuthenticationHandler(handler); - try + var result = await httpContext.AuthenticateAsync(AuthenticationScheme); + if (result.Succeeded) { - await _next(httpContext); - } - finally - { - DetachAuthenticationhandler(handler); - } - } - else - { - await _next(httpContext); - } - } - - private WindowsPrincipal UpdateUser(HttpContext httpContext) - { - var tokenHeader = httpContext.Request.Headers[MSAspNetCoreWinAuthToken]; - - int hexHandle; - WindowsPrincipal winPrincipal = null; - if (!StringValues.IsNullOrEmpty(tokenHeader) - && int.TryParse(tokenHeader, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexHandle)) - { - // Always create the identity if the handle exists, we need to dispose it so it does not leak. - var handle = new IntPtr(hexHandle); - var winIdentity = new WindowsIdentity(handle); - - // WindowsIdentity just duplicated the handle so we need to close the original. - NativeMethods.CloseHandle(handle); - - httpContext.Response.RegisterForDispose(winIdentity); - winPrincipal = new WindowsPrincipal(winIdentity); - - if (_options.AutomaticAuthentication) - { - // Don't get it from httpContext.User, that always returns a non-null anonymous user by default. - var existingPrincipal = httpContext.Features.Get()?.User; - if (existingPrincipal != null) - { - httpContext.User = SecurityHelper.MergeUserPrincipal(existingPrincipal, winPrincipal); - } - else - { - httpContext.User = winPrincipal; - } + httpContext.User = result.Principal; } } - return winPrincipal; - } - - private void AttachAuthenticationHandler(AuthenticationHandler handler) - { - var auth = handler.HttpContext.Features.Get(); - if (auth == null) - { - auth = new HttpAuthenticationFeature(); - handler.HttpContext.Features.Set(auth); - } - handler.PriorHandler = auth.Handler; - auth.Handler = handler; - } - - private void DetachAuthenticationhandler(AuthenticationHandler handler) - { - var auth = handler.HttpContext.Features.Get(); - if (auth != null) - { - auth.Handler = handler.PriorHandler; - } + await _next(httpContext); } } } diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IISOptions.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IISOptions.cs index 9bceb66be4..c37ad2aef4 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/IISOptions.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/IISOptions.cs @@ -1,21 +1,10 @@ -// 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. -using System.Collections.Generic; -using Microsoft.AspNetCore.Http.Authentication; -using Microsoft.AspNetCore.Server.IISIntegration; - namespace Microsoft.AspNetCore.Builder { public class IISOptions { - /// - /// If true the authentication middleware alter the request user coming in and respond to generic challenges. - /// If false the authentication middleware will only provide identity and respond to challenges when explicitly indicated - /// by the AuthenticationScheme. - /// - public bool AutomaticAuthentication { get; set; } = true; - /// /// If true authentication middleware will try to authenticate using platform handler windows authentication /// If false authentication middleware won't be added @@ -26,20 +15,5 @@ namespace Microsoft.AspNetCore.Builder /// Populates the ITLSConnectionFeature if the MS-ASPNETCORE-CLIENTCERT request header is present. /// public bool ForwardClientCertificate { get; set; } = true; - - /// - /// Additional information about the authentication type which is made available to the application. - /// - public IList AuthenticationDescriptions { get; } = new List() - { - new AuthenticationDescription() - { - AuthenticationScheme = IISDefaults.Negotiate - }, - new AuthenticationDescription() - { - AuthenticationScheme = IISDefaults.Ntlm - } - }; } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.csproj b/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.csproj index 77091fee0e..770455059e 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.csproj +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs index ac9c19abc6..36718f8394 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs @@ -1,7 +1,8 @@ -// 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. using System; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpOverrides; @@ -58,6 +59,7 @@ namespace Microsoft.AspNetCore.Hosting { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; }); + services.AddAuthenticationCore(); }); } diff --git a/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs b/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs index 5144ccc71f..7cf950eeb1 100644 --- a/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs +++ b/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [ConditionalTheory] [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] - [InlineData(RuntimeArchitecture.x64, ApplicationType.Portable, Skip = "https://github.com/aspnet/ServerTests/issues/82")] + [InlineData(RuntimeArchitecture.x64, ApplicationType.Portable)] public Task NtlmAuthentication(RuntimeArchitecture architecture, ApplicationType applicationType) { return NtlmAuthentication(ServerType.IISExpress, architecture, applicationType); @@ -76,6 +76,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("Anonymous?True", responseText); + /* Disabled for due to https://github.com/aspnet/ServerTests/issues/82 response = await httpClient.GetAsync("/Restricted"); responseText = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); @@ -92,6 +93,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests 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); @@ -101,6 +103,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests 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); + response = await httpClient.GetAsync("/AutoForbid"); responseText = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/IISMiddlewareTests.cs b/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/IISMiddlewareTests.cs index 9b55dd2a8a..5dbb652b14 100644 --- a/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/IISMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/IISMiddlewareTests.cs @@ -1,9 +1,10 @@ -// 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. using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.Features.Authentication; @@ -152,13 +153,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration .UseIISIntegration() .Configure(app => { - app.Run(context => + app.Run(async context => { - var auth = context.Features.Get(); - Assert.NotNull(auth); - Assert.Equal("Microsoft.AspNetCore.Server.IISIntegration.AuthenticationHandler", auth.Handler.GetType().FullName); + var auth = context.RequestServices.GetRequiredService(); + var windows = await auth.GetSchemeAsync(IISMiddleware.AuthenticationScheme); + Assert.NotNull(windows); + Assert.Null(windows.DisplayName); + Assert.Equal("Microsoft.AspNetCore.Server.IISIntegration.AuthenticationHandler", windows.HandlerType.FullName); assertsExecuted = true; - return Task.FromResult(0); }); }); var server = new TestServer(builder); @@ -170,8 +172,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration Assert.True(assertsExecuted); } - [Fact] - public async Task DoesNotAddAuthenticationHandlerIfWindowsAuthDisabled() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task OnlyAddAuthenticationHandlerIfForwardWindowsAuthentication(bool forward) { var assertsExecuted = false; @@ -184,15 +188,61 @@ namespace Microsoft.AspNetCore.Server.IISIntegration { services.Configure(options => { - options.ForwardWindowsAuthentication = false; + options.ForwardWindowsAuthentication = forward; + }); + }) + .Configure(app => + { + app.Run(async context => + { + var auth = context.RequestServices.GetService(); + Assert.NotNull(auth); + var windowsAuth = await auth.GetSchemeAsync(IISMiddleware.AuthenticationScheme); + if (forward) + { + Assert.NotNull(windowsAuth); + Assert.Null(windowsAuth.DisplayName); + Assert.Equal("AuthenticationHandler", windowsAuth.HandlerType.Name); + } + else + { + Assert.Null(windowsAuth); + } + assertsExecuted = true; + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.TryAddWithoutValidation("MS-ASPNETCORE-TOKEN", "TestToken"); + await server.CreateClient().SendAsync(req); + + Assert.True(assertsExecuted); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task DoesNotBlowUpWithoutAuth(bool forward) + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .UseSetting("TOKEN", "TestToken") + .UseSetting("PORT", "12345") + .UseSetting("APPL_PATH", "/") + .UseIISIntegration() + .ConfigureServices(services => + { + services.Configure(options => + { + options.ForwardWindowsAuthentication = forward; }); }) .Configure(app => { app.Run(context => { - var auth = context.Features.Get(); - Assert.Null(auth); assertsExecuted = true; return Task.FromResult(0); }); diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/Microsoft.AspNetCore.Server.IISIntegration.Tests.csproj b/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/Microsoft.AspNetCore.Server.IISIntegration.Tests.csproj index dc6bf32c48..028b94020a 100644 --- a/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/Microsoft.AspNetCore.Server.IISIntegration.Tests.csproj +++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/Microsoft.AspNetCore.Server.IISIntegration.Tests.csproj @@ -11,6 +11,7 @@ + diff --git a/test/TestSites/StartupNtlmAuthentication.cs b/test/TestSites/StartupNtlmAuthentication.cs index 13033e7ed5..480563ecb5 100644 --- a/test/TestSites/StartupNtlmAuthentication.cs +++ b/test/TestSites/StartupNtlmAuthentication.cs @@ -1,11 +1,12 @@ -// 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. using System; using System.Security.Principal; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Authentication; +using Microsoft.AspNetCore.Server.IISIntegration; using Microsoft.Extensions.Logging; using Xunit; @@ -51,30 +52,18 @@ namespace TestSites } else { - return context.Authentication.ChallengeAsync(); + return context.ChallengeAsync(); } } if (context.Request.Path.Equals("/Forbidden")) { - return context.Authentication.ForbidAsync(AuthenticationManager.AutomaticScheme); + return context.ForbidAsync(); } if (context.Request.Path.Equals("/AutoForbid")) { - return context.Authentication.ChallengeAsync(); - } - - if (context.Request.Path.Equals("/RestrictedNegotiate")) - { - if (string.Equals("Negotiate", context.User.Identity.AuthenticationType, StringComparison.Ordinal)) - { - return context.Response.WriteAsync("Negotiate"); - } - else - { - return context.Authentication.ChallengeAsync("Negotiate"); - } + return context.ChallengeAsync(); } if (context.Request.Path.Equals("/RestrictedNTLM")) @@ -85,7 +74,7 @@ namespace TestSites } else { - return context.Authentication.ChallengeAsync("NTLM"); + return context.ChallengeAsync(IISMiddleware.AuthenticationScheme); } } @@ -93,4 +82,4 @@ namespace TestSites }); } } -} \ No newline at end of file +}