78

I am using IdentityServer4.

I want to add other custom claims to access token but I'm unable to do this. I have modified Quickstart5 and added ASP.NET Identity Core and the custom claims via ProfileService as suggested by Coemgen below.

You can download my code here: [zip package][3]. (It is based on: Quickstart5 with ASP.NET Identity Core and added claims via ProfileService).

Issue: GetProfileDataAsync does not executed.

001
  • 62,807
  • 94
  • 230
  • 350
  • I'm rather perplexed by this question, 001. You're clearly a very experienced user of Stack Overflow, but you've put your code in a file locker rather than in the question, and thus it is - as you must know - off topic. Are you able to repair the question, so it is not put on hold? – halfer Sep 20 '17 at 20:11

4 Answers4

121

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

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>();
David Klempfner
  • 8,700
  • 20
  • 73
  • 153
KJBTech
  • 1,901
  • 2
  • 15
  • 27
  • 5
    thanks for that, however, it still does not work! no claims are added! – 001 Jun 28 '17 at 09:04
  • Are you target the GetProfileDataAsync function in debug mode ? – KJBTech Jun 28 '17 at 09:10
  • I am viewing the claims on the "secure" page here https://github.com/IdentityServer/IdentityServer4.Samples/blob/release/Quickstarts/5_HybridFlowAuthenticationWithApiAccess/src/MvcClient/Controllers/HomeController.cs – 001 Jun 28 '17 at 10:13
  • What happens when you target your API ? What are the claims ? – KJBTech Jun 28 '17 at 10:14
  • All claims passed on the secure page is the same claims passed by the api. And they do not include the claims i added above via "ProfileService" – 001 Jun 28 '17 at 10:19
  • Okay, but if you add a break point, did you target GetProfileDataAsync ? – KJBTech Jun 28 '17 at 10:20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/147806/discussion-between-coemgen-and-001). – KJBTech Jun 28 '17 at 10:24
  • 1
    On startup, it executes this " services.AddTransient(); //AddClaims" but it break point, does not execute GetProfileDataAsync method – 001 Jun 28 '17 at 10:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/147911/discussion-between-001-and-coemgen). – 001 Jun 29 '17 at 09:53
  • If that does not work you may try to delete cookies for that site or at least to log out from your application and IdentityServer – infografnet Jan 28 '20 at 14:32
  • See the answer of 001 below to have something that works ;-) – ssougnez Aug 11 '20 at 20:04
  • I had a problem where it wasn't being added to the ServicesCollection. I had to move the services.AddTransient above the AddIdentityServer. – Bluebaron May 03 '21 at 01:11
  • if you are calling _userManager.GetUserAsync in Login method to raise UserLoginSuccessEvent then _userManager.GetUserAsync will called twice? hitting DB twice? – Sadiq Khoja Sep 10 '21 at 03:56
  • This doesn't work for client_credentials – Stamos Apr 12 '22 at 08:21
55

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.

Community
  • 1
  • 1
cleftheris
  • 4,626
  • 38
  • 55
45

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
001
  • 62,807
  • 94
  • 230
  • 350
  • 2
    That's interresting. You also should be able to use services.AddTransient(); . – KJBTech Jun 29 '17 at 10:14
  • 4
    There is a great example on the Microsoft Architecture GitHub repository : https://github.com/dotnet-architecture/eShopOnContainers/blob/master/src/Services/Identity/Identity.API/Startup.cs – AdrienTorris Jun 29 '17 at 10:14
  • 2
    @Coemgen you can do that too! but you must add " services.AddTransient();" after "services.AddIdentityServer()" :) – 001 Jun 29 '17 at 10:20
  • You can simply do this services.AddTransient(); and that will work – Rob L Sep 22 '17 at 10:13
  • 1
    I believe that if you want to stick with `services.AddTransient();` you should do that after adding identityserver to services so your registration will override that one made by IS – pushist1y Feb 10 '18 at 11:03
  • This works perfectly! Thank you. – NET Experts May 04 '20 at 18:42
  • for anyone reading, order matters and the profileService should be registered last. – kennydust Apr 24 '22 at 04:04
2

You can include any claim by using UserClaims option in your GetIdentityResources() in the config class :

UserClaims: List of associated user claim types that should be included in the identity token. (As per the official documentation) http://docs.identityserver.io/en/release/reference/identity_resource.html#refidentityresource

JayDeeEss
  • 1,075
  • 9
  • 14