Search code examples
authenticationidentityserver4

TenantId Claim not being returned from IdentityServer4 to Client App


Following on from this question, I ended up using the HttpContext.SignInAsync(string subject, Claim[] claims) overload to pass the selected tenant id as a claim (defined as type "TenantId") after the user selects a Tenant.

I then check for this claim in my custom AccountChooserResponseGenerator class from this question to determine if the user needs to be directed to the Tenant Chooser page or not, as follows:

public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request, ConsentResponse consent = null)
        {
            var response = await base.ProcessInteractionAsync(request, consent);
            if (response.IsConsent || response.IsLogin || response.IsError)
                return response;

            if (!request.Subject.HasClaim(c=> c.Type == "TenantId" && c.Value != "0"))
                return new InteractionResponse
                {
                    RedirectUrl = "/Tenant"
                };

            return new InteractionResponse();
        }

The interaction is working and the user gets correctly redirected back to the Client app after selecting a Tenant.

However, on my client, I have the simple:

<dl>
            @foreach (var claim in User.Claims)
            {
                <dt>@claim.Type</dt>
                <dd>@claim.Value</dd>
            }
        </dl>

snippet from the IdentityServer4 quickstarts to show the claims, and sadly, my TenantId claim is not there.

I have allowed for it in the definition of my Client on my IdentityServer setup, as follows:

var client = new Client
                {
... other settings here
                    AllowedScopes = new List<string>
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.Email,
                        IdentityServerConstants.StandardScopes.Phone,
                        "TenantId"
                    }
                };

What am I missing in order for this TenantId claim to become visible in my Client application?

EDIT:

Based on @d_f's comments, I have now added TentantId to my server's GetIdentityResources(), as follows:

public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email(),
                new IdentityResources.Phone(),
                new IdentityResource("TenantId", new[] {"TenantId"})
            };
        }

And I have edited the client's startup.ConfigureServices(IServiceCollection services) to request this additional scope, as follows:

services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            })
                .AddCookie("Cookies")
                .AddOpenIdConnect("oidc", options =>
                {
                    //other settings not shown

                    options.Scope.Add("TenantId");

                });

And still the only claims displayed on the client by the indicated snippet are: claims

Edit 2: Fixed!

Finally @RichardGowan's answer worked. And that is because (as brilliantly observed by @AdemCaglin) I was using IdentityServer's AspNetIdentity, which has it's own implementation of IProfileService, which kept dropping my custom TenantId claim, despite ALL these other settings).

So in the end, I could undo all those other settings...I have no mention of the TenantId claim in GetIdentityResources, no mention of it in AllowedScopes in the definition of the Client in my IdSrv, and no mention of it in the configuration of services.AddAuthentication on my client.


Solution

  • You will need to provide and register an implementation of IProfileService to issue your custom claim back to the client:

    public class MyProfileService : IProfileService {
        public MyProfileService() {
        }
    
        public Task GetProfileDataAsync(ProfileDataRequestContext context) {
            // Issue custom claim
            context.IssuedClaims.Add(context.Subject.Claims.First(c => c.Type == 
                   "TenantId"));
            return Task.CompletedTask;
        }
    
        public Task IsActiveAsync(IsActiveContext context) {
            context.IsActive = true;
            return Task.CompletedTask;
        }
    }