TenantId Claim not being returned from IdentityServer4 to Client App

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



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.


HttpContext.SignInAsync(string subject, Claim claims)



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:


AccountChooserResponseGenerator


public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request, ConsentResponse consent = null)
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:


GetIdentityResources()


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:


startup.ConfigureServices(IServiceCollection services)


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.


GetIdentityResources


AllowedScopes


services.AddAuthentication





You've added "TenantId" as AllowedScope for your client, but you've not described that scope. Something like: services.AddIdentityServer() .AddInMemoryIdentityResources(new List<IdentityResource> new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResources.Email(), new IdentityResource("TenantId", new "TenantId") )
– d_f
Aug 7 at 16:53





and you have to request that additional scope in your client configuration
– d_f
Aug 7 at 16:57





or simply .AddInMemoryIdentityResources(GetIdentityResources()) and later: public static List<IdentityResource> GetIdentityResources() /*Claims automatically included in OpenId scope*/ var openIdScope = new IdentityResources.OpenId(); openIdScope.UserClaims.Add("TenantId"); /* Available scopes*/ return new List<IdentityResource>openIdScope, new IdentityResources.Profile(), new IdentityResources.Email(), /*etc*/;
– d_f
Aug 7 at 17:48



.AddInMemoryIdentityResources(GetIdentityResources())


public static List<IdentityResource> GetIdentityResources() /*Claims automatically included in OpenId scope*/ var openIdScope = new IdentityResources.OpenId(); openIdScope.UserClaims.Add("TenantId"); /* Available scopes*/ return new List<IdentityResource>openIdScope, new IdentityResources.Profile(), new IdentityResources.Email(), /*etc*/;





@d_f thanks for your feedback...please see my edits indicating changes made to my setup...but still no TenantId claim on the client side?
– Shawn de Wet
Aug 8 at 2:43





Add AlwaysIncludeUserClaimsInIdToken setting to your client's configuration in IdSrv.
– d_f
Aug 8 at 11:53



AlwaysIncludeUserClaimsInIdToken




1 Answer
1



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


IProfileService


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;






That's not necessary. The DefaultProfileService implementation already does that job. Custom profile service is amid for filtering or for issuing totally new claims not yet within the Subject.
– d_f
Aug 8 at 12:04





Yes this worked! Finally!
– Shawn de Wet
Aug 8 at 15:47





As I mentioned, the default IProfileService implementation makes the same thing, since it has context.AddRequestedClaims(context.Subject.Claims); inside. Seems, you performed some additional change or fix to make it work...
– d_f
Aug 8 at 16:27



IProfileService


context.AddRequestedClaims(context.Subject.Claims);





@d_f Probably OP uses identityserver with identity and this library has own iprofileservice implementation. You can see code github.com/IdentityServer/IdentityServer4.AspNetIdentity/blob/…
– adem caglin
Aug 8 at 18:34





@ademcaglin wow what a brilliant observation...I was not aware of that either! But that is exactly it.
– Shawn de Wet
Aug 9 at 3:21






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard