dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.2k stars 9.94k forks source link

Making HttpContext.User available to 3rd party code without Microsoft.AspNetCore.Http dependency #34636

Open robertmclaws opened 3 years ago

robertmclaws commented 3 years ago

Background and Motivation

On AspNetCore, when you need to access the HttpContext in a "service" class, you use an IHttpContextAccessor to get the thread-safe instance of the HttpContext used by the current thread, thanks to its internal use of AsyncLocal.

However, if you need to be able to access the HttpContext.User, AND the service class is in another assembly, you can no longer rely on ClaimsPrincipal.Current or Thread.CurrentPrincipal to get you that information. If you control the external assembly, you're now dependent on Microsoft.AspNetCore.Http in all of your libraries, which is unnecessary.

Proposed API

The proposal is a stand-alone class that does not affect the behavior or functionality of any existing APIs, which should make it quick to approve and get into the current release, even given the current timeframe for .NET 6's release.

The solution is to implement an identical pattern to IHttpContextAccessor for IPrincipal. This will require an Interface to be added to System.Security, and an HttpContextPrincipalAccessor in Microsoft.AspNetCore.Http.

namespace Microsoft.AspNetCore.Authentication
{
    public class AuthenticationOptions
    {
+       bool EnableThreadCurrentPrincipal { get; set; }
    }
}

Because the shipped implementation takes an IHttpContextAccessor in the constructor, it will pull in the thread's HttpContext to correctly pull the user from.

Usage Examples

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddAuthentication(options => 
             {
                  options.EnableThreadCurrentPrincipal = true;
                  options.DefaultScheme = "Cookies";
             });
    }
}

Alternative Designs

Risks

I have the requisite pull requests ready to submit if the team is OK with this.

mqudsi commented 1 year ago

AsyncLocal is basically impossible to use correctly without mentally (or on a whiteboard!) desugaring all async/await into a state machine to figure out what the “span” of an AsyncLocal value is.

I wrote an entire async locking library around AsyncLocal and it works great and is used by a lot of people but I am still not comfortable writing greenfield code w/ AsyncLocal, truth be told.

Is there some way of getting Roslyn to spit out the desugared C# (instead of reversing the IL to C#)?