Skip to content
This repository was archived by the owner on Nov 1, 2018. It is now read-only.

Commit 8ed21d5

Browse files
committed
IIS => Auth 2.0
1 parent 5761ddd commit 8ed21d5

File tree

10 files changed

+160
-229
lines changed

10 files changed

+160
-229
lines changed

samples/IISSample/Startup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Linq;
33
using Microsoft.AspNetCore.Builder;
44
using Microsoft.AspNetCore.Hosting;
Lines changed: 60 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,104 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Linq;
6-
using System.Security.Claims;
5+
using System.Globalization;
6+
using System.Security.Principal;
77
using System.Threading.Tasks;
8-
using Microsoft.AspNetCore.Builder;
8+
using Microsoft.AspNetCore.Authentication;
99
using Microsoft.AspNetCore.Http;
10-
using Microsoft.AspNetCore.Http.Authentication;
11-
using Microsoft.AspNetCore.Http.Features.Authentication;
1210
using Microsoft.Extensions.Internal;
11+
using Microsoft.Extensions.Primitives;
1312

1413
namespace Microsoft.AspNetCore.Server.IISIntegration
1514
{
1615
internal class AuthenticationHandler : IAuthenticationHandler
1716
{
18-
internal AuthenticationHandler(HttpContext httpContext, IISOptions options, ClaimsPrincipal user)
19-
{
20-
HttpContext = httpContext;
21-
User = user;
22-
Options = options;
23-
}
24-
25-
internal HttpContext HttpContext { get; }
17+
private const string MSAspNetCoreWinAuthToken = "MS-ASPNETCORE-WINAUTHTOKEN";
18+
private WindowsPrincipal _user;
19+
private HttpContext _context;
2620

27-
internal IISOptions Options { get; }
21+
internal AuthenticationScheme Scheme { get; private set; }
2822

29-
internal ClaimsPrincipal User { get; }
30-
31-
internal IAuthenticationHandler PriorHandler { get; set; }
32-
33-
public Task AuthenticateAsync(AuthenticateContext context)
23+
public Task<AuthenticateResult> AuthenticateAsync()
3424
{
35-
if (ShouldHandleScheme(context.AuthenticationScheme))
25+
var user = GetUser();
26+
if (user != null)
3627
{
37-
if (User != null)
38-
{
39-
context.Authenticated(User, properties: null, description: null);
40-
}
41-
else
42-
{
43-
context.NotAuthenticated();
44-
}
28+
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, Scheme.Name)));
4529
}
46-
47-
if (PriorHandler != null)
30+
else
4831
{
49-
return PriorHandler.AuthenticateAsync(context);
32+
return Task.FromResult(AuthenticateResult.None());
5033
}
51-
52-
return TaskCache.CompletedTask;
5334
}
5435

55-
public Task ChallengeAsync(ChallengeContext context)
36+
private WindowsPrincipal GetUser()
5637
{
57-
// Some other provider may have already accepted this challenge. Having multiple providers with
58-
// AutomaticChallenge = true is considered invalid, but changing the default would breaking
59-
// normal Windows auth users.
60-
if (!context.Accepted && ShouldHandleScheme(context.AuthenticationScheme))
38+
if (_user == null)
6139
{
62-
switch (context.Behavior)
63-
{
64-
case ChallengeBehavior.Automatic:
65-
// If there is a principal already, invoke the forbidden code path
66-
if (User == null)
67-
{
68-
goto case ChallengeBehavior.Unauthorized;
69-
}
70-
else
71-
{
72-
goto case ChallengeBehavior.Forbidden;
73-
}
74-
case ChallengeBehavior.Unauthorized:
75-
HttpContext.Response.StatusCode = 401;
76-
// We would normally set the www-authenticate header here, but IIS does that for us.
77-
break;
78-
case ChallengeBehavior.Forbidden:
79-
HttpContext.Response.StatusCode = 403;
80-
break;
81-
}
82-
context.Accept();
83-
}
40+
var tokenHeader = _context.Request.Headers[MSAspNetCoreWinAuthToken];
8441

85-
if (PriorHandler != null)
86-
{
87-
return PriorHandler.ChallengeAsync(context);
88-
}
42+
int hexHandle;
43+
if (!StringValues.IsNullOrEmpty(tokenHeader)
44+
&& int.TryParse(tokenHeader, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexHandle))
45+
{
46+
// Always create the identity if the handle exists, we need to dispose it so it does not leak.
47+
var handle = new IntPtr(hexHandle);
48+
var winIdentity = new WindowsIdentity(handle);
8949

90-
return TaskCache.CompletedTask;
91-
}
50+
// WindowsIdentity just duplicated the handle so we need to close the original.
51+
NativeMethods.CloseHandle(handle);
9252

93-
public void GetDescriptions(DescribeSchemesContext context)
94-
{
95-
foreach (var description in Options.AuthenticationDescriptions)
96-
{
97-
context.Accept(description.Items);
53+
_context.Response.RegisterForDispose(winIdentity);
54+
_user = new WindowsPrincipal(winIdentity);
55+
}
9856
}
9957

100-
if (PriorHandler != null)
101-
{
102-
PriorHandler.GetDescriptions(context);
103-
}
58+
return _user;
10459
}
10560

106-
public Task SignInAsync(SignInContext context)
61+
62+
public Task ChallengeAsync(ChallengeContext context)
10763
{
108-
// Not supported, fall through
109-
if (PriorHandler != null)
64+
switch (context.Behavior)
11065
{
111-
return PriorHandler.SignInAsync(context);
66+
case ChallengeBehavior.Automatic:
67+
// If there is a principal already, invoke the forbidden code path
68+
if (GetUser() == null)
69+
{
70+
goto case ChallengeBehavior.Unauthorized;
71+
}
72+
else
73+
{
74+
goto case ChallengeBehavior.Forbidden;
75+
}
76+
case ChallengeBehavior.Unauthorized:
77+
context.HttpContext.Response.StatusCode = 401;
78+
// We would normally set the www-authenticate header here, but IIS does that for us.
79+
break;
80+
case ChallengeBehavior.Forbidden:
81+
context.HttpContext.Response.StatusCode = 403;
82+
break;
11283
}
113-
11484
return TaskCache.CompletedTask;
11585
}
11686

117-
public Task SignOutAsync(SignOutContext context)
87+
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
11888
{
119-
// Not supported, fall through
120-
if (PriorHandler != null)
121-
{
122-
return PriorHandler.SignOutAsync(context);
123-
}
124-
89+
Scheme = scheme;
90+
_context = context;
12591
return TaskCache.CompletedTask;
12692
}
12793

128-
private bool ShouldHandleScheme(string authenticationScheme)
94+
public Task SignInAsync(SignInContext context)
12995
{
130-
if (Options.AutomaticAuthentication && string.Equals(AuthenticationManager.AutomaticScheme, authenticationScheme, StringComparison.Ordinal))
131-
{
132-
return true;
133-
}
96+
throw new NotSupportedException();
97+
}
13498

135-
return Options.AuthenticationDescriptions.Any(description => string.Equals(description.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal));
99+
public Task SignOutAsync(SignOutContext context)
100+
{
101+
return TaskCache.CompletedTask;
136102
}
137103
}
138104
}

src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs

Lines changed: 17 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.Diagnostics;
6-
using System.Globalization;
7-
using System.Security.Principal;
7+
using System.Linq;
88
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Authentication;
910
using Microsoft.AspNetCore.Builder;
1011
using Microsoft.AspNetCore.Http;
1112
using Microsoft.AspNetCore.Http.Features;
12-
using Microsoft.AspNetCore.Http.Features.Authentication;
13-
using Microsoft.Extensions.Internal;
1413
using Microsoft.Extensions.Logging;
1514
using Microsoft.Extensions.Options;
1615
using Microsoft.Extensions.Primitives;
@@ -19,7 +18,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
1918
{
2019
public class IISMiddleware
2120
{
22-
private const string MSAspNetCoreWinAuthToken = "MS-ASPNETCORE-WINAUTHTOKEN";
21+
public static readonly string AuthenticationScheme = "Windows";
22+
2323
private const string MSAspNetCoreClientCert = "MS-ASPNETCORE-CLIENTCERT";
2424
private const string MSAspNetCoreToken = "MS-ASPNETCORE-TOKEN";
2525

@@ -28,7 +28,7 @@ public class IISMiddleware
2828
private readonly ILogger _logger;
2929
private readonly string _pairingToken;
3030

31-
public IISMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<IISOptions> options, string pairingToken)
31+
public IISMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<IISOptions> options, string pairingToken, IAuthenticationSchemeProvider authentication)
3232
{
3333
if (next == null)
3434
{
@@ -49,6 +49,13 @@ public IISMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOption
4949

5050
_next = next;
5151
_options = options.Value;
52+
53+
54+
if (_options.ForwardWindowsAuthentication)
55+
{
56+
authentication.AddScheme(new AuthenticationScheme(AuthenticationScheme, displayName: null, handlerType: typeof(AuthenticationHandler)));
57+
}
58+
5259
_pairingToken = pairingToken;
5360
_logger = loggerFactory.CreateLogger<IISMiddleware>();
5461
}
@@ -80,80 +87,14 @@ public async Task Invoke(HttpContext httpContext)
8087

8188
if (_options.ForwardWindowsAuthentication)
8289
{
83-
var winPrincipal = UpdateUser(httpContext);
84-
var handler = new AuthenticationHandler(httpContext, _options, winPrincipal);
85-
AttachAuthenticationHandler(handler);
86-
try
87-
{
88-
await _next(httpContext);
89-
}
90-
finally
91-
{
92-
DetachAuthenticationhandler(handler);
93-
}
94-
}
95-
else
96-
{
97-
await _next(httpContext);
98-
}
99-
}
100-
101-
private WindowsPrincipal UpdateUser(HttpContext httpContext)
102-
{
103-
var tokenHeader = httpContext.Request.Headers[MSAspNetCoreWinAuthToken];
104-
105-
int hexHandle;
106-
WindowsPrincipal winPrincipal = null;
107-
if (!StringValues.IsNullOrEmpty(tokenHeader)
108-
&& int.TryParse(tokenHeader, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexHandle))
109-
{
110-
// Always create the identity if the handle exists, we need to dispose it so it does not leak.
111-
var handle = new IntPtr(hexHandle);
112-
var winIdentity = new WindowsIdentity(handle);
113-
114-
// WindowsIdentity just duplicated the handle so we need to close the original.
115-
NativeMethods.CloseHandle(handle);
116-
117-
httpContext.Response.RegisterForDispose(winIdentity);
118-
winPrincipal = new WindowsPrincipal(winIdentity);
119-
120-
if (_options.AutomaticAuthentication)
90+
var result = await httpContext.AuthenticateAsync(AuthenticationScheme);
91+
if (result.Succeeded)
12192
{
122-
// Don't get it from httpContext.User, that always returns a non-null anonymous user by default.
123-
var existingPrincipal = httpContext.Features.Get<IHttpAuthenticationFeature>()?.User;
124-
if (existingPrincipal != null)
125-
{
126-
httpContext.User = SecurityHelper.MergeUserPrincipal(existingPrincipal, winPrincipal);
127-
}
128-
else
129-
{
130-
httpContext.User = winPrincipal;
131-
}
93+
httpContext.User = result.Principal;
13294
}
13395
}
13496

135-
return winPrincipal;
136-
}
137-
138-
private void AttachAuthenticationHandler(AuthenticationHandler handler)
139-
{
140-
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
141-
if (auth == null)
142-
{
143-
auth = new HttpAuthenticationFeature();
144-
handler.HttpContext.Features.Set(auth);
145-
}
146-
handler.PriorHandler = auth.Handler;
147-
auth.Handler = handler;
148-
}
149-
150-
private void DetachAuthenticationhandler(AuthenticationHandler handler)
151-
{
152-
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
153-
if (auth != null)
154-
{
155-
auth.Handler = handler.PriorHandler;
156-
}
97+
await _next(httpContext);
15798
}
15899
}
159100
}
Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System.Collections.Generic;
5-
using Microsoft.AspNetCore.Http.Authentication;
6-
using Microsoft.AspNetCore.Server.IISIntegration;
7-
84
namespace Microsoft.AspNetCore.Builder
95
{
106
public class IISOptions
117
{
12-
/// <summary>
13-
/// If true the authentication middleware alter the request user coming in and respond to generic challenges.
14-
/// If false the authentication middleware will only provide identity and respond to challenges when explicitly indicated
15-
/// by the AuthenticationScheme.
16-
/// </summary>
17-
public bool AutomaticAuthentication { get; set; } = true;
18-
198
/// <summary>
209
/// If true authentication middleware will try to authenticate using platform handler windows authentication
2110
/// If false authentication middleware won't be added
@@ -26,20 +15,5 @@ public class IISOptions
2615
/// Populates the ITLSConnectionFeature if the MS-ASPNETCORE-CLIENTCERT request header is present.
2716
/// </summary>
2817
public bool ForwardClientCertificate { get; set; } = true;
29-
30-
/// <summary>
31-
/// Additional information about the authentication type which is made available to the application.
32-
/// </summary>
33-
public IList<AuthenticationDescription> AuthenticationDescriptions { get; } = new List<AuthenticationDescription>()
34-
{
35-
new AuthenticationDescription()
36-
{
37-
AuthenticationScheme = IISDefaults.Negotiate
38-
},
39-
new AuthenticationDescription()
40-
{
41-
AuthenticationScheme = IISDefaults.Ntlm
42-
}
43-
};
4418
}
4519
}

src/Microsoft.AspNetCore.Server.IISIntegration/Microsoft.AspNetCore.Server.IISIntegration.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14+
<PackageReference Include="Microsoft.AspNetCore.Authentication.Core" Version="$(AspNetCoreVersion)" />
1415
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(AspNetCoreVersion)" />
1516
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(AspNetCoreVersion)" />
1617
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(AspNetCoreVersion)" />

0 commit comments

Comments
 (0)