How to add custom claims to access token in IdentityServer4?

Ok the issue here is this:

although you have configured your available Identity resources correctly (both standard & custom), you also need to explicitly define which ones are a necessity when calling your api resource. In order to define this you must go to your Config.cs class on ExampleIdentityServer project and provide a third argument like on the new ApiResouirce constructor. Only those will be included into the access_token

// scopes define the API resources in your system
public static IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        new ApiResource("api1", "My API", new[] { JwtClaimTypes.Subject, JwtClaimTypes.Email, JwtClaimTypes.Phone, etc... })
    };
}

In essence this means that I got my identity claims configured for my organization but there may be more than one APIs involved and not all of the APIs make use of all available profile claims. This also means that these will be present inside your ClaimsPrincipal all the rest can still be accessed through the "userinfo" endpoint as a normal http call.

NOTE: regarding refresh tokens:

If you chose to enable refresh tokens via AllowOfflineAccess = true, you may experience the same behavior upon refreshing the access_token "GetProfileDataAsync does not executed!". So the claims inside the access_token stay the same although you get a new access_token with updated lifetime. If that is the case you can force them to always refresh from the Profile service by setting UpdateAccessTokenClaimsOnRefresh=true on the client configuration.


You should implement your own ProfileService. Have a look in this post which I followed when I implemented the same:

https://damienbod.com/2016/11/18/extending-identity-in-identityserver4-to-manage-users-in-asp-net-core/

Here is an example of my own implementation:

public class ProfileService : IProfileService
{
    protected UserManager<ApplicationUser> _userManager;

    public ProfileService(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        //>Processing
        var user = await _userManager.GetUserAsync(context.Subject);

        var claims = new List<Claim>
        {
            new Claim("FullName", user.FullName),
        };

        context.IssuedClaims.AddRange(claims);
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        //>Processing
        var user = await _userManager.GetUserAsync(context.Subject);
        
        context.IsActive = (user != null) && user.IsActive;
    }
}

Don't forget to configure the service in your Startup.cs (via this answer)

services.AddIdentityServer()
    .AddProfileService<ProfileService>();

Issue found.

In startup.cs, instead of adding services.AddTransient<IProfileService, ProfileService>();, add .AddProfileService<ProfileService>() to services.AddIdentityServer().

You will end up with

services.AddIdentityServer()
    .AddTemporarySigningCredential()
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApiResources())
    .AddInMemoryClients(Config.GetClients())
    .AddAspNetIdentity<ApplicationUser>()
    .AddProfileService<ProfileService>();

Thanks for Coemgen for helping out! Nothing wrong with the code, just the startup was wrong.