Reputation: 1035
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
Upvotes: 6
Views: 4003
Reputation: 1487
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!
Upvotes: 7