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?

3 Answers 3

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>();

Sign up to request clarification or add additional context in comments.

2 Comments

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)
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<ApplicationUser>().AddProfileService<CustomProfileService>();
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);

2 Comments

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<Claim> { new Claim("given_name", model.FirstName), new Claim("family_name", model.LastName) });
This may help some folks if doing SPA: stackoverflow.com/questions/61201244/…
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 protected]"));

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);
}

2 Comments

Thanks, where does additionalClaims come from, it's not declared in my method or controller?
@AdrianThompsonPhillips It's just a new List<Claim>() I created. Look at the samplse where await HttpContext.Authentication.SignInAsync(user.SubjectId, user.Username, provider, props, additionalClaims.ToArray()); is called

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.