Skip to content

Commit c42f0a0

Browse files
authored
Ensure ClaimsPrincipal.Current is set if user changes (#631)
The existing setup would only set Current to the existing user. Some scenarios seem to invoke at other times, so this uses the IRequestUserFeature to ensure that any updates to the user will be mirrored to the ClaimsUser.Current
1 parent 6de2392 commit c42f0a0

File tree

24 files changed

+386
-288
lines changed

24 files changed

+386
-288
lines changed

samples/AuthRemoteIdentity/AuthRemoteIdentityCore/Program.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,32 @@
100100

101101
app.UseRouting();
102102

103-
app.UseAuthenticationEvents();
104103
app.UseAuthentication();
105-
app.UseAuthorizationEvents();
104+
app.UseAuthenticationEvents();
106105
app.UseAuthorization();
106+
app.UseAuthorizationEvents();
107107

108108
app.UseSystemWebAdapters();
109109

110110
app.MapDefaultControllerRoute();
111111

112+
app.Map("/user", () =>
113+
{
114+
var user = ClaimsPrincipal.Current;
115+
116+
if (user is null)
117+
{
118+
return Results.Problem("Empty ClaimsPrincipal");
119+
}
120+
121+
return Results.Json(new
122+
{
123+
IsAuthenticated = user.Identity?.IsAuthenticated ?? false,
124+
Name = user.Identity?.Name,
125+
Claims = user.Claims.Select(c => new { c.Type, c.Value })
126+
});
127+
}).WithMetadata(new SetThreadCurrentPrincipalAttribute()); ;
128+
112129
app.MapRemoteAppFallback()
113130
.ShortCircuit();
114131

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/CurrentPrincipalMiddleware.cs

Lines changed: 0 additions & 45 deletions
This file was deleted.

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/CachePolicyMiddleware.cs renamed to src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/Middleware/CachePolicyMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System.Threading.Tasks;
55
using Microsoft.AspNetCore.Http;
66

7-
namespace Microsoft.AspNetCore.SystemWebAdapters;
7+
namespace Microsoft.AspNetCore.SystemWebAdapters.Middleware;
88

99
internal sealed class CachePolicyMiddleware(RequestDelegate next)
1010
{
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Http.Features;
7+
using Microsoft.AspNetCore.SystemWebAdapters.Features;
8+
9+
namespace Microsoft.AspNetCore.SystemWebAdapters.Middleware;
10+
11+
internal sealed class CurrentPrincipalMiddleware(RequestDelegate next)
12+
{
13+
public Task InvokeAsync(HttpContextCore context)
14+
{
15+
if (context.GetEndpoint()?.Metadata.GetMetadata<SetThreadCurrentPrincipalAttribute>() is { IsDisabled: false })
16+
{
17+
context.Features.GetRequiredFeature<IRequestUserFeature>().EnableStaticAccessors();
18+
}
19+
20+
return next(context);
21+
}
22+
}

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/PreBufferRequestStreamMiddleware.cs renamed to src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/Middleware/PreBufferRequestStreamMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using Microsoft.AspNetCore.Http.Features;
77
using Microsoft.AspNetCore.SystemWebAdapters.Features;
88

9-
namespace Microsoft.AspNetCore.SystemWebAdapters;
9+
namespace Microsoft.AspNetCore.SystemWebAdapters.Middleware;
1010

1111
internal partial class PreBufferRequestStreamMiddleware
1212
{
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Http.Features;
7+
using Microsoft.AspNetCore.SystemWebAdapters.Features;
8+
9+
namespace Microsoft.AspNetCore.SystemWebAdapters.Middleware;
10+
11+
internal sealed class RequestFeaturesMiddleware(RequestDelegate next)
12+
{
13+
public async Task InvokeAsync(HttpContextCore context)
14+
{
15+
var existing = context.Features.GetRequiredFeature<IHttpRequestFeature>();
16+
var existingPipe = context.Features.Get<IRequestBodyPipeFeature>();
17+
18+
using var inputStreamFeature = new HttpRequestInputStreamFeature(existing);
19+
20+
context.Features.Set<IHttpRequestFeature>(inputStreamFeature);
21+
context.Features.Set<IHttpRequestInputStreamFeature>(inputStreamFeature);
22+
context.Features.Set<IRequestBodyPipeFeature>(inputStreamFeature);
23+
context.Features.Set<IHttpRequestPathFeature>(inputStreamFeature);
24+
25+
try
26+
{
27+
await next(context);
28+
}
29+
finally
30+
{
31+
context.Features.Set<IHttpRequestFeature>(existing);
32+
context.Features.Set<IRequestBodyPipeFeature>(existingPipe);
33+
context.Features.Set<IHttpRequestInputStreamFeature>(null);
34+
context.Features.Set<IHttpRequestPathFeature>(null);
35+
}
36+
}
37+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Security.Claims;
6+
using System.Security.Principal;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Http;
10+
using Microsoft.AspNetCore.Http.Features.Authentication;
11+
using Microsoft.AspNetCore.SystemWebAdapters.Features;
12+
using Microsoft.Extensions.Logging;
13+
using Microsoft.Extensions.Options;
14+
15+
namespace Microsoft.AspNetCore.SystemWebAdapters.Middleware;
16+
17+
internal sealed partial class RequestUserFeaturesMiddleware(RequestDelegate next, IOptions<SystemWebAdaptersOptions> options, ILoggerFactory loggerFactory)
18+
{
19+
private readonly ILogger _logger = loggerFactory.CreateLogger<RequestUserFeature>();
20+
private readonly bool _setCurrentAccessors = options.Value.EnableStaticUserAccessors;
21+
22+
public async Task InvokeAsync(HttpContextCore context, IOptions<SystemWebAdaptersOptions> options)
23+
{
24+
using var userFeature = new RequestUserFeature(_logger, _setCurrentAccessors) { User = context.User };
25+
26+
context.Features.Set<IRequestUserFeature>(userFeature);
27+
context.Features.Set<IHttpAuthenticationFeature>(userFeature);
28+
29+
try
30+
{
31+
await next(context);
32+
}
33+
finally
34+
{
35+
context.Features.Set<IRequestUserFeature>(null);
36+
context.Features.Set<IHttpAuthenticationFeature>(null);
37+
}
38+
}
39+
40+
private sealed partial class RequestUserFeature(ILogger logger, bool setCurrentAccessors) : IRequestUserFeature, IHttpAuthenticationFeature, IDisposable
41+
{
42+
private readonly ILogger logger = logger;
43+
44+
[LoggerMessage(0, LogLevel.Debug, "A custom principal {PrincipalType} is being used and should be replaced with a ClaimsPrincipal derived type.")]
45+
private partial void LogNonClaimsPrincipal(Type principalType);
46+
47+
[LoggerMessage(1, LogLevel.Trace, "Thread.CurrentPrincipal has been set with the current user")]
48+
private partial void LogCurrentPrincipalUsage();
49+
50+
public IPrincipal? User { get; set; }
51+
52+
WindowsIdentity? IRequestUserFeature.LogonUserIdentity => User?.Identity as WindowsIdentity;
53+
54+
ClaimsPrincipal? IHttpAuthenticationFeature.User
55+
{
56+
get => GetOrCreateClaims(User);
57+
set
58+
{
59+
User = value;
60+
EnsureCurrentPrincipalSetIfRequired();
61+
}
62+
}
63+
64+
private ClaimsPrincipal? GetOrCreateClaims(IPrincipal? principal)
65+
{
66+
if (principal is null)
67+
{
68+
return null;
69+
}
70+
71+
if (principal is ClaimsPrincipal claimsPrincipal)
72+
{
73+
return claimsPrincipal;
74+
}
75+
76+
LogNonClaimsPrincipal(principal.GetType());
77+
78+
return new ClaimsPrincipal(principal);
79+
}
80+
81+
public void EnableStaticAccessors()
82+
{
83+
LogCurrentPrincipalUsage();
84+
setCurrentAccessors = true;
85+
EnsureCurrentPrincipalSetIfRequired();
86+
}
87+
88+
private void EnsureCurrentPrincipalSetIfRequired()
89+
{
90+
if (setCurrentAccessors)
91+
{
92+
var claimsPrincipal = GetOrCreateClaims(User);
93+
Thread.CurrentPrincipal = claimsPrincipal;
94+
}
95+
}
96+
97+
public void Dispose()
98+
{
99+
if (setCurrentAccessors)
100+
{
101+
Thread.CurrentPrincipal = null;
102+
}
103+
}
104+
}
105+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Http.Features;
7+
using Microsoft.AspNetCore.SystemWebAdapters.Features;
8+
9+
namespace Microsoft.AspNetCore.SystemWebAdapters.Middleware;
10+
11+
internal sealed class ResponseFeaturesMiddleware(RequestDelegate next)
12+
{
13+
public async Task InvokeAsync(HttpContextCore context)
14+
{
15+
var responseBodyFeature = context.Features.GetRequiredFeature<IHttpResponseBodyFeature>();
16+
17+
using var adapterFeature = new HttpResponseAdapterFeature(responseBodyFeature);
18+
19+
context.Features.Set<IHttpResponseBodyFeature>(adapterFeature);
20+
context.Features.Set<IHttpResponseBufferingFeature>(adapterFeature);
21+
context.Features.Set<IHttpResponseEndFeature>(adapterFeature);
22+
context.Features.Set<IHttpResponseContentFeature>(adapterFeature);
23+
24+
try
25+
{
26+
await next(context);
27+
}
28+
finally
29+
{
30+
// The buffering feature may be removed if the response has ended i.e in usage with YARP
31+
if (context.Features.Get<IHttpResponseBufferingFeature>() is { } buffer)
32+
{
33+
await buffer.FlushAsync();
34+
}
35+
36+
context.Features.Set<IHttpResponseBodyFeature>(responseBodyFeature);
37+
context.Features.Set<IHttpResponseBufferingFeature>(null);
38+
context.Features.Set<IHttpResponseEndFeature>(null);
39+
context.Features.Set<IHttpResponseContentFeature>(null);
40+
}
41+
}
42+
}

src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionEventsMiddleware.cs renamed to src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/Middleware/SessionEventsMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using Microsoft.AspNetCore.Http.Features;
77
using Microsoft.AspNetCore.SystemWebAdapters.Features;
88

9-
namespace Microsoft.AspNetCore.SystemWebAdapters;
9+
namespace Microsoft.AspNetCore.SystemWebAdapters.Middleware;
1010

1111
internal sealed class SessionEventsMiddleware
1212
{

0 commit comments

Comments
 (0)