Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core 2.0.3 ClaimsTransformer in combination with HttpContextAccessor, claims are being cleared

In my Asp.Net Core application I want to add custom claims to my ClaimsIdentity so I can access these in different layers of my application. To achieve this I added following code

Startup

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddTransient<IPrincipal>(
            provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);
        services.AddTransient<IClaimsTransformation, ClaimsTransformer>();

ClaimsTransformer

public class ClaimsTransformer : IClaimsTransformation
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IPrincipal _principal;
    public ClaimsTransformer(IUnitOfWork unitOfWork, IPrincipal principal)
    {
        _unitOfWork = unitOfWork;
        _principal = principal;
    }
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var currentPrincipal  = (ClaimsIdentity)_principal.Identity;
        var identity = (ClaimsIdentity)principal.Identity;
        if (currentPrincipal.Claims.All(p => p.Type != "UserId"))
        {
            var person = _unitOfWork.PersonRepository.GetPersonBySubjectId(principal.Claims.First(p => p.Type == "sub").Value);
            person.Wait();
            if (person.Result != null)
            {
                currentPrincipal.AddClaim(new Claim("UserId", person.Result.Id.ToString()));
                currentPrincipal.AddClaim(new Claim("TenantId", person.Result.PersonTeams.FirstOrDefault(p => p.Team.TeamType == TeamType.OrganizationTeam)?.Team.OrganizationId.ToString()));
                if (principal.Claims.Any(p => p.Type == "Admin"))
                {
                    currentPrincipal.AddClaim(new Claim("Admin", "True"));
                }
            }
            foreach (var claim in identity.Claims)
            {
                currentPrincipal.AddClaim(claim);
            }
        }
        return Task.FromResult(principal);
    }
}

What I don't understand is, when I run my Claimstransformation and I step through the code, all the needed claims are available, but when I inject my IPrincipal into a custom class, the claims collection is empty, when I don't use the ClaimsTransformation, the claims are available via the injected IPrincipal.

To resolve this issue, I add my IPrincipal to the ClaimsTransformer and duplicate the claims from the TransformAsync input parameter and add the UserId and TenantId. This works, but the problem I have is that I don't understand why the Claims are being deleted when I run the ClaimsTransformer and why I need to add this hack

like image 551
iBoonZ Avatar asked Nov 17 '17 20:11

iBoonZ


People also ask

What is HttpContextAccessor .NET core?

It stores the request and response information, such as the properties of request, request-related services, and any data to/from the request or errors, if there are any. ASP.NET Core applications access the HTTPContext through the IHttpContextAccessor interface. The HttpContextAccessor class implements it.

How use HttpContext current in .NET core?

Current property, instead it is available in the HttpContext class in ASP.Net Core applications. Session can be enabled using the Configure method. Inside this method, you will have to call the UseSession method of the app object. Note: It is mandatory to call the UseSession method before the UseMvc method.

Should I use IHttpContextAccessor?

It's only necessary to use IHttpContextAccessor when you need access to the HttpContext inside a service.

What is ClaimsPrincipal C#?

ClaimsPrincipal exposes a collection of identities, each of which is a ClaimsIdentity. In the common case, this collection, which is accessed through the Identities property, will only have a single element.

What are claims in ASP NET Core Identity?

I also authorized only users of a particular role to access an Controller/Action. ASP.NET Core Identity offers claims for doing authentication and authorization and this tutorial will cover Claims in details Claims are a name-value pair issued by a third party. For example, a person driving licence is issued by a driving license authority.

What is ASP NET Core claims based authorization?

Claims-based authorization in ASP.NET Core. When an identity is created it may be assigned one or more claims issued by a trusted party. A claim is a name value pair that represents what the subject is, not what the subject can do.

How do I access httpcontext in ASP NET Core?

ASP.NET Core apps access HttpContext through the IHttpContextAccessor interface and its default implementation HttpContextAccessor. It's only necessary to use IHttpContextAccessor when you need access to the HttpContext inside a service. The Razor Pages PageModel exposes the HttpContext property:

What is an ASP NET claim?

Claims can be created from any user or identity data which can be issued using a trusted identity provider or ASP.NET Core identity. A claim is a name value pair that represents what the subject is, not what the subject can do. This article covers the following areas:


1 Answers

I was in the same place. I had to remove the IPrincipal DI

Startup

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();

services.AddAuthentication(IISDefaults.AuthenticationScheme);

services.AddAuthorization(options =>
        {
            options.AddPolicy("SystemAdminOnly", policy => policy.RequireClaim(ClaimTypes.Role, "SystemAdmin"));
        });

ClaimsTransformer

public ClaimsTransformer(IRepository repository, IHttpContextAccessor httpContextAccessor/*, IPrincipal principal*/, IMemoryCache cache)
    {
        _repository = repository;
        _httpContextAccessor = httpContextAccessor;
       // _principal = principal;
        _cache = cache;
    }

public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
   {
        if (principal.Identity.IsAuthenticated)
        {
            var currentPrincipal = (ClaimsIdentity)principal.Identity;//_principal.Identity;

            var ci = (ClaimsIdentity)principal.Identity;
            var cacheKey = ci.Name;

            if (_cache.TryGetValue(cacheKey, out List<Claim> claims))
            {
                currentPrincipal.AddClaims(claims);
            }
            else
            {
                claims = new List<Claim>();
                var isUserSystemAdmin = await _repository.IsUserAdmin(ci.Name);
                if (isUserSystemAdmin)
                {
                    var c = new Claim(ClaimTypes.Role, "SystemAdmin");
                    claims.Add(c);
                }

                _cache.Set(cacheKey, claims);
                currentPrincipal.AddClaims(claims);
            }

            //foreach (var claim in ci.Claims)
            //{
            //    currentPrincipal.AddClaim(claim);
            //}
        }

        return await Task.FromResult(principal);
    }

And it works!

like image 139
Steve Tolba Avatar answered Oct 16 '22 22:10

Steve Tolba