20

I am using IdentityServer4 and I am trying to add a custom default claim to my CLIENT when the token is created. This is possible if i use the implicit flow and IProfileService like shown below.

public class MyProfileService : IProfileService
{
    public MyProfileService()
    {

    }
    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var claims = new List<Claim>
        {
            new Claim("DemoClaimType", "DemoClaimValue")
        };
        context.IssuedClaims = claims;
        return Task.FromResult(0);
    }
    public Task IsActiveAsync(IsActiveContext context)
    {
        context.IsActive = true;
        return Task.FromResult(0);
    }
}

And in my startup

services.AddIdentityServer()
            .AddProfileService<MyProfileService>()

However this does not work with my client with client_credential granttype since it seems cannot request OpenID scopes in client credentials flow. It turns out Iprofileservice like the name implies works for Identity Resources where the OpenId scopes like profile is valid. since am unable to request profile scopes with client_credential grant type GetProfileDataAsync never gets called.

Since am working only with clients and no users I need a way to inject claims into the token without having to add them to the client object like below

    new Client
{
    ClientId = "myclient",
    ClientName = "My Client",
    AllowedGrantTypes = GrantTypes.ClientCredentials,
    ClientSecrets = {new Secret("secret".Sha256())},
    AllowedScopes = new List<string> {"api"},                    
    AllowOfflineAccess = true,

    //I Don't want to do this
    Claims = new List<Claim>
    {   
        new Claim("type","value")
    }
}

I do not want the above because i do not want the the claim to be part of the client_claims in the database. I need to create it on the fly on token request. I hope my question is clearer now.

flexxxit
  • 2,440
  • 5
  • 42
  • 69
  • The `IProfileService` get's called when an `access_token` is being generated - it's used to add claims to it. – moritzg May 10 '17 at 13:42
  • Also we're adding the `IProfileService` like this: `services.AddTransient();` (inside Startup > ConfigureServices) maybe that's your problem? – moritzg May 10 '17 at 13:43
  • 1
    `AddProfileService` is the correct method. – Mardoxx May 10 '17 at 14:15
  • 1
    @moritzg What i want is to Add to the default client claims (like for example there exists `client_id` which is not in the claims list for the client) using the Iprofileservice. – flexxxit May 11 '17 at 07:44
  • 1
    @Mardoxx The constructor is getting called but GetProfileDataAsync method is not getting called. – flexxxit May 11 '17 at 07:47
  • Maybe i should be implementing the IClaimsService rather than IProfileService? I realise that the Default Claims class uses this interface. – flexxxit May 11 '17 at 08:01
  • I think, not 100% sure, you need to either call userinfo endpoint (configure OIDC client to do this if you are using one) or set client's `AlwaysIncludeUserClaimsInIdToken = true` – Mardoxx May 11 '17 at 08:46
  • @Mardoxx But that deals with `User` information. I am working with only clients – flexxxit May 11 '17 at 09:21

2 Answers2

24

With some investigation I finally found out how to do this. I needed a way to add claims dynamically to the client when token was requested.

In order to do that I had to extend ICustomTokenRequestValidator and then include my class in Startup.cs thorough dependency injection

public class DefaultClientClaimsAdder : ICustomTokenRequestValidator
{
    public Task ValidateAsync(CustomTokenRequestValidationContext context)
    {
        context.Result.ValidatedRequest.Client.AlwaysSendClientClaims = true;
        context.Result.ValidatedRequest.ClientClaims.Add(new Claim("testtoken","testbody"));

        return Task.CompletedTask;
    }
}

Configure services in Startup.cs

 services.AddTransient<ICustomTokenRequestValidator, DefaultClientClaimsAdder>();
stuzor
  • 2,275
  • 1
  • 31
  • 45
flexxxit
  • 2,440
  • 5
  • 42
  • 69
  • 3
    Worth noted: with the latest version in IDS4 branch v1.x I had to use `context.Result.ValidatedRequest.ClientClaims` instead, else wouldn't work for me. – Learner Mar 26 '18 at 09:11
4

Alternatively, you can use ClientStore to add new claims into clients.

public class YourClientStore : IClientStore
{
    private readonly DbContext _context;
    private readonly IMapper _mapper;
    public YourClientStore(DbContext context,
        IMapper mapper)
    {
        _context= context;
        _mapper = mapper;
    }

    public Task<Client> FindClientByIdAsync(string clientId)
    {
        var dbClient = _context.Clients.AsQueryable()
            .Where(x => x.ClientId == clientId)
            .FirstOrDefault();
        var client = _mapper.Map<Client>(dbClient);
        if (client != null)
        {
            client.Claims.Add(new Claim("<your claim name>", "<your claim value>"));
        }
        return Task.FromResult(client);
    }
}
adem caglin
  • 22,700
  • 10
  • 58
  • 78
  • I ended up using the DbContext of type `ConfigurationDbContext` which is provided by IdentityServer, and then in my configuration of IdentityServer (after `AddConfigurationStore`) I added the line `.AddClientStore()` to enable the custom store. – nover Feb 14 '19 at 10:34