Search code examples
c#.net-coredependency-injection.net-6.0claims-based-identity

Accessing Claims Principal in the Service layer in the API of a Net core 6 App


I need to access ClaimsPrincipal within the service layer of a Net Core 6 app.

I could always just builder.Services.AddTransient<IHttpContextAccessor, HttpContextAccessor>(); in the Startup.cs & go my merry way but this is a no-no. Makes it difficult to test and more importantly this is a great example of leaky abstraction.

So, now what I have is the following

  public class ClaimsProvider : IClaimsProvider
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        public ClaimsProvider(IHttpContextAccessor httpContextAccessor)  
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public ClaimsPrincipal? GetClaimsPrincipal()
        {
            return _httpContextAccessor.HttpContext?.User;
        }
    }

    public interface IClaimsProvider
    {
        ClaimsPrincipal? GetClaimsPrincipal();
    }

Within my Startup.cs AddScoped() that takes an IHttpContextAccessor and return an IClaimsProvider. Then I simply build all services against IClaimsProvider

builder.Services.AddScoped<IClaimsProvider>(provider =>
{
    var httpContextAccessor = provider.GetRequiredService<IHttpContextAccessor>();
    return new ClaimsProvider(httpContextAccessor);
});

And the usual route for my services where I inject it as a dependency

      private readonly IClaimsProvider _claimsProvider;
        public SomeService(
            IWebHostEnvironment hostingEnvironment,
            IMapper mapper, IClaimsProvider claimsProvider, ...)
        {
            _hostingEnvironment = hostingEnvironment ?? 
                throw new ArgumentNullException(nameof(hostingEnvironment));
            _mapper = mapper ?? 
                throw new ArgumentNullException(nameof(mapper));
            _claimsProvider = claimsProvider;
        }   

        public void SomeMethod() 
        {
            var u = _claimsProvider.GetClaimsPrincipal();
            foreach (var claim in u.Claims)
            {
                Console.WriteLine($"{claim.Type} : {claim.Value}");
            }

        }

My question is that is the above approach ok? Potentially, is there any other approach that is better than the one shown above?


Solution

  • To prevent a leaky abstract (the need for an IHttpContextAsccessor in your service), I would recommend using the Adapter Pattern.

    public void ConfigureServices(IServiceCollection services)
    {
      services.AddMvc();
      services.AddHttpContextAccessor();
      services.AddScoped<IClaimsProvider, HttpContextClaimsProvider>();
    }
     
    public IClaimsProvider
    {
      public ClaimsPrinciple ClaimsPrinciple { get; }
    }
     
    // Adapter
    public HttpContextClaimsProvider : IClaimsProvider
    {
      public HttpContextClaimsProvider(IHttpContextAccessor httpContext)
      {
        ClaimsProvider = httpContext?.User?.Principle as ClaimsPrinciple;
      }
      public ClaimsPrinciple ClaimsPrinciple { get; private set; }
     
    }
     
     
    public class YourService : IYourService 
    {
      private readonly IClaimsProvider _claimsProvider;
     
      public YourService(IClaimsProvider claimsProvider)
      {
        _claimsProvider= claimsProvider;
      }
    }