Inspired by an article on custom claims, I’ve added a tenant id custom claim to my Identity server sign in process as follows:
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using MyNamespace.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using MyNamespace.Data;
using MyNamespace.Constants;
namespace MyNamespace.Factories
{
public class TenantClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
public TenantClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
IOptions<IdentityOptions> optionsAccessor)
: base(userManager, optionsAccessor) {
}
// TODO: Remove hard binding to application db context
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user) {
var identity = await base.GenerateClaimsAsync(user);
var tenantId = ApplicationDbContext.DefaultTenantId;
if (user.TenantId != Guid.Empty) {
tenantId = user.TenantId;
}
identity.AddClaim(new Claim(CustomClaimTypes.TenantId, tenantId.ToString()));
return identity;
}
}
}
The claims generating method is executed at login and claims are added to the identity, so this part seems ok. Later I try to read out the claim later in my tenant provider service as follows
using System;
using MyNamespace.Data;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using System.Linq;
using MyNamespace.Constants;
namespace MyNamespace.Services
{
public interface ITenantProvider
{
Guid GetTenantId();
}
public class TenantProvider : ITenantProvider
{
private IHttpContextAccessor _httpContextAccessor;
public TenantProvider(IHttpContextAccessor httpContextAccessor
{
_httpContextAccessor = httpContextAccessor;
}
// TODO: Remove hard binding to application db context
public Guid GetTenantId()
{
var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
var user = _httpContextAccessor.HttpContext.User;
var tenantId = _httpContextAccessor.HttpContext.User.FindFirst(CustomClaimTypes.TenantId).Value;
Guid tenantGuid = ApplicationDbContext.DefaultTenantId;
Guid.TryParse(tenantId, out tenantGuid);
return tenantGuid;
}
}
}
As far as I understand, however, the claim identified by CustomClaimTypes.TenantId is not automatically mapped by the Identity server. My question is this: how can I map
options.ClaimActions.MapUniqueJsonKey(CustomClaimTypes.TenantId, CustomClaimTypes.TenantId);
from Startup.cs where I add the Identity server the my dependencies:
services.AddAuthentication()
.AddIdentityServerJwt();
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
So, in the end I ended up with a different solution than what I sought originally. Instead of mapping the claims as created by the factory, I came across another post here at StackOverflow. Basically, what I did was the following. I implemented the following ProfileService
namespace MyNamespace.Services
{
public class ProfileService : IProfileService
{
protected UserManager<ApplicationUser> _userManager;
public ProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = await _userManager.GetUserAsync(context.Subject);
var claims = new List<Claim>
{
new Claim(CustomClaimTypes.TenantId, user.TenantId.ToString()),
};
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
var user = await _userManager.GetUserAsync(context.Subject);
context.IsActive = (user != null) && user.IsActive;
}
}
}
Then, I added the service in the DI Container at Configure:
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
.AddProfileService<ProfileService>();
services.AddAuthentication()
.AddIdentityServerJwt();
So, I still have a good time letting AddIdentityServerJwt setting up IdentityServer4, while getting my claims at the same time.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0