3

I'm using Identity Server 4 and I've customised my ASP.NET Identity user as follows:

public class ApplicationUser : IdentityUser
{
    [MaxLength(100)]
    public virtual string FirstName { get; set; }

    [MaxLength(100)]
    public virtual string LastName { get; set; }
}

I can't see where I would configure Identity Server 4 to include these 2 properties in the claims collection. I've had a look through some of the Identity Server 4 samples but can't see any examples.

I'd ideally like to map these 2 user properties to the given_name and family_name claims.

I'm currently hooking up to the notifications and querying the userinfo endpoint (hybrid flow?). So I'm not sure if this is configuration of Identity Server or customization of the userinfo endpoint?

Adrian Thompson Phillips
  • 6,893
  • 6
  • 38
  • 69

3 Answers3

9

In order to include your custom claims, you need to implement your own GetProfileDataAsync() method using the IProfileService. This method is being called everytime a user claim is requested.

Here is my implementation of IProfileService.

public class CustomProfileService : IProfileService
{
    private readonly UserManager<User> _userManager;

    public CustomProfileService(UserManager<User> userManager)
    {
        _userManager = userManager;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var subjectId = context.Subject.GetSubjectId();
        var user = await _userManager.FindByIdAsync(subjectId);

        if (user == null) return;

        var claims = new List<Claim>
        {
            new Claim("username", user.UserName),
            new Claim("email", user.Email),
            new Claim("firstname", user.FirstName),
            new Claim("lastname", user.LastName)
        };

        var roles = await _userManager.GetRolesAsync(user);
        foreach (var role in roles)
        {
            claims.Add(new Claim("role", role));
        }

        var userClaims = await _userManager.GetClaimsAsync(user);
        foreach (var userClaim in userClaims)
        {
            claims.Add(new Claim(userClaim.Type, userClaim.Value));
        }

        context.IssuedClaims = claims;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        var user = await _userManager.FindByIdAsync(context.Subject.GetSubjectId());
        context.IsActive = user.IsActive;
    }
}

Then you will have to add this following line to Startup.ConfigureServices()

services.AddScoped<IProfileService, CustomProfileService>();

George
  • 6,630
  • 2
  • 29
  • 36
arzzzae
  • 161
  • 4
  • 2
    This looks like the perfect solution but I cannot see that my custom profile service is ever called. Breakpoints added on ctor and all methods - nothing (and yes - it is wired in properly in DI) – Keith Jackson Jul 30 '20 at 09:28
  • Instead of simply adding it as a scoped service, you need to add it to IdentityServer by using the `AddProfileService` extension method on the `IIdentityServerBuilder`. E.g. `services.AddIdentityServerDefaults().AddProfileService();` – brenwebber Aug 01 '22 at 18:26
3

I was wondering why there is no documentation on this. It lead me to realise that I'm probably doing it wrong.

I'd not seen the table AspNetUserClaims created as part of ASP.NET Identity. I added my claim data into here and the claim data pulls through as part of the profile.

In the POST method for AccountController.Register I added:

var givenNameClaim = new IdentityUserClaim<string>()
{
    ClaimType = "given_name",
    ClaimValue = model.FirstName
};

var familyNameClaim = new IdentityUserClaim<string>()
{
    ClaimType = "family_name",
    ClaimValue = model.LastName
};

var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
user.Claims.Add(givenNameClaim);
user.Claims.Add(familyNameClaim);
Adrian Thompson Phillips
  • 6,893
  • 6
  • 38
  • 69
  • 1
    If you are using aspnet identity to manage user then this would be the correct answer. since you already have a IProfileService implementation that will use these values. I had to make a slight change when creating claims since I am using 2.1. `await _userManager.AddClaimsAsync(user, new List { new Claim("given_name", model.FirstName), new Claim("family_name", model.LastName) });` – sarvesh Aug 07 '18 at 01:00
  • This may help some folks if doing SPA: https://stackoverflow.com/questions/61201244/identify-roles-with-spa-and-net-core-3 – Chris Feb 03 '22 at 20:08
0

Here's what I've done: In AccountController.ExternalLoginCallback I added this:

//Add claim even if client didn't ask for it
additionalClaims.Add(new Claim(JwtClaimTypes.Email, "email@email.com"));

then I added the claim to the access_token by dependency injecting my ProfileService class and adding the claims in the MyProfileService.GetProfileDataAsync like this:

public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
    var claims = new List<Claim>();

    Claim emailClaim = context.Subject.Claims.Where<Claim>(claim => claim.Type.Equals(JwtClaimTypes.Email)).FirstOrDefault();

    if (emailClaim != null)
    {
        claims.Add(emailClaim);
    }

    context.IssuedClaims = claims;
    return Task.FromResult(0);
}
moritzg
  • 4,266
  • 3
  • 37
  • 62
  • Thanks, where does `additionalClaims` come from, it's not declared in my method or controller? – Adrian Thompson Phillips Jun 07 '17 at 07:36
  • @AdrianThompsonPhillips It's just a `new List()` I created. Look at the samplse where `await HttpContext.Authentication.SignInAsync(user.SubjectId, user.Username, provider, props, additionalClaims.ToArray());` is called – moritzg Jun 07 '17 at 08:40