Search code examples
identityserver4claims

Identity Server 4: How to add Custom Claims only for UserInfoEndpoint and exclude them in AccessToken?


We want to serve the large amount of data in the user claims from the UserInfoEndPoint but we don't want to embed those claims in AccessToken.

As far as I know, we can return the additional data from UserInfoEndPoint when we want to keep the AccessToken size small. Ref: Profile Service

So, I implemented the IProfileService as per following:

public class ProfileService : IProfileService
{
    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var sub = context.Subject.GetSubjectId();

        // Get data from Db
        var claims = new List<Claim>();

        claims.Add(new Claim("global_company_id", "88888888-D964-4A2B-8D56-B893A5BCD700"));
        //..... add series of additional claims

        context.IssuedClaims = claims;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        var sub = context.Subject.GetSubjectId();

        context.IsActive = true;
    }
}

It returns the extended claims from the UserInfoEndpoint. But, the problem is that these set of claims are also included in the Jwt Access Token too and it makes the token a lot bigger unnecessarily.

{
  "nbf": 1582236568,
  "exp": 1582236868,
  "iss": "https://localhost:44378",
  "aud": [
    "https://localhost:44378/resources",
    "testapi"
  ],
  "client_id": "testClientId",
  "sub": "78452916-D260-4219-927C-954F4E987E70",
  "auth_time": 1582236558,
  "idp": "local",
  "name": "ttcg",
  "global_company_id": "88888888-D964-4A2B-8D56-B893A5BCD700",
  //........ series of claims here
  "scope": [
    "openid",
    "profile",
    "address",
    "roles",
    "country",
    "customClaims"
  ],
  "amr": [
    "pwd"
  ]
}

Here is my Client Configuration in Identity Server Provider:

var clientUrl = "http://localhost:64177";
            return new Client
            {
                ClientName = "Test Web Application",
                ClientId = "testClientId",
                AllowedGrantTypes = GrantTypes.Hybrid,
                AllowOfflineAccess = false,
                RequireConsent = false,
                RedirectUris = new List<string>
                    {
                        $"{clientUrl}/signin-oidc"
                    },
                PostLogoutRedirectUris = new List<string>
                    {
                        $"{clientUrl}/signout-callback-oidc"
                    },
                AllowedScopes = new List<string>
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.OfflineAccess,
                        "t1_global_ids"
                    },
                ClientSecrets =
                    {
                        new Secret("abc123".Sha256())
                    }
            };

Here is my MVC .Net Core Client configuration which connects to Identity Server

services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            })
            .AddCookie("Cookies", options =>
            {
                options.AccessDeniedPath = "/AccessDenied";
            })
            .AddOpenIdConnect("oidc", options =>
            {
                options.SignInScheme = "Cookies";
                options.Authority = "http://identityserverUrl";
                options.ClientId = "testClientId";
                options.ResponseType = "code id_token";

                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("CustomClaims"); // <-- here

                options.SaveTokens = true;
                options.ClientSecret = "abc123";
                options.GetClaimsFromUserInfoEndpoint = true;

            });

Could you pls help me about it and let me know whether it's possible to hide these claims in token or not?


Solution

  • I played around with this once and I found a solution, but it might be considered "hacky" -- it worked but I never used it in production, so use at your own risk.

    The GetProfileDataAsync() method of your ProfileService gets called at various times -- when the JWT is created, when the UserEndpoint is hit, etc. In this case, you DO NOT want your custom claims to be added when creating the JWT, so create a conditional that doesn't add them when the "Caller" is the JWT creation process (which is of type "ClaimsProviderAccessToken").

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
       if (context.Caller != IdentityServerConstants.ProfileDataCallers.ClaimsProviderAccessToken)
       {
          var sub = context.Subject.GetSubjectId();
    
          // Get data from Db
          var claims = new List<Claim>();
    
          claims.Add(new Claim("global_company_id", "88888888-D964-4A2B-8D56-B893A5BCD700"));
          //..... add series of additional claims
    
          context.IssuedClaims = claims;
       }
    }
    

    With this, the JWT does not contain your custom claims, but if you hit the UserEndpoint it will return those user's claims as part of the JSON.