tony09uk
tony09uk

Reputation: 2991

Identity server getting custom in memory IdentityResources

after searching for two days to trying and identify where I am going wrong, I have accepted that I need some help to point me in the right direction.

I'm at the really early stages of working with Identity server, still simply using inMemory clients and scopes, just to get my head around what is happening and how it all links together.

I am trying to return a list of custom claims to my angular application from Identity server, but I am failing. I've tried extending IProfileService, which successfully adds the custom claim but it removes the other claims, that I defined in my TestUser

With MyProfileService registered

With MyProfileService

Without MyProfileService registered

Without MyProfileService

Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddIdentityServer()
                .AddDeveloperSigningCredential()
                .AddInMemoryApiResources(Resources.GetApiResources())
                .AddInMemoryIdentityResources(Resources.GetIdentityResources())
                .AddInMemoryClients(Clients.Get())
                .AddTestUsers(Users.Get())
                .AddDeveloperSigningCredential();

        //services.AddTransient<IProfileService, MyProfileService>();

        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();

#if DEBUG
        app.UseDeveloperExceptionPage();
#endif

        app.UseIdentityServer();

        app.UseStaticFiles();

        app.UseMvcWithDefaultRoute();
    }

MyProfileService.cs

public class MyProfileService : IProfileService
{
    public MyProfileService()
    {
    }

    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        // Issue custom claim
        context.IssuedClaims.Add(new System.Security.Claims.Claim("TenantId", "123456"));
        return Task.CompletedTask;
    }

    public Task IsActiveAsync(IsActiveContext context)
    {
        context.IsActive = true;
        return Task.CompletedTask;
    }
}

Resources.cs

public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource> {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResource {
                Name = "role",
                UserClaims = new List<string> {"role"}
            },
            new IdentityResource
            {
                Name = "tenant.info",
                DisplayName = "Tenant Information",
                UserClaims = new List<string>
                {
                    "tenantid",
                    "subscriptionid"
                }
            }
        };
    }

    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource> {
            new ApiResource("api1", "api1")
        };
    }

Users.cs

    public static List<TestUser> Get()
    {
        return new List<TestUser> {
            new TestUser {
                SubjectId = "5BE86359-073C-434B-AD2D-A3932222DABE",
                Username = "scott",
                Password = "password",
                Claims = new List<Claim>
                {
                    new Claim("tenantid", "123456"),
                    new Claim(JwtClaimTypes.Name, "Scott xxxxx"),
                    new Claim(JwtClaimTypes.GivenName, "Scott"),
                    new Claim(JwtClaimTypes.FamilyName, "xxxxx"),
                    new Claim(JwtClaimTypes.Email, "[email protected]"),
                    new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                    new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
                    new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json)
                }

            }
        };
    }

Clients.cs

public static IEnumerable<Client> Get()
    {
        return new List<Client> {
            new Client {
                ClientId = "angular_spa",
                ClientName = "Angular 4 Client",
                AllowedGrantTypes = GrantTypes.Code,
                RequirePkce = true,
                RequireClientSecret = false,
                AllowedScopes = new List<string> {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "api1"
                },
                RedirectUris = new List<string> { "http://localhost:4200/admin/loggedin" },
                PostLogoutRedirectUris = new List<string> { "http://localhost:4200/admin/loggedout" },
                AllowedCorsOrigins = new List<string> { "http://localhost:4200" },
                AllowAccessTokensViaBrowser = true
            }
        };
    }

EDIT: Additional failed solutions

  1. Add default behaviour to MyProfileService (as suggested by the answer from Ruard van Elburg)

    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
    context.AddRequestedClaims(context.Subject.Claims);
    context.IssuedClaims.Add(new System.Security.Claims.Claim("tenantId", "123456"));
    }
    

    Result in client: shows the tenantId but no other claims that I set on my TestUser

    profile:
    amr: ["pwd"]
    auth_time: 1553024858
    idp: "local"
    sid: "34f36d1c0056ad3d65d1671e339e73aa"
    sub: "5BE86359-073C-434B-AD2D-A3932222DABE"
    tenantId: "123456"
    __proto__: Object
    
  2. Add subject.claims to issedClaims

    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        context.IssuedClaims.Add(new System.Security.Claims.Claim("tenantId", "123456"));
        context.IssuedClaims.AddRange(context.Subject.Claims);
    }
    

    Result in client: shows the tenantId and name (which is referring to the username) but no claims that I set on my TestUser

    profile:
    amr: ["pwd"]
    auth_time: 1553025311
    idp: "local"
    name: "scott"
    sid: "831a89053b54f3df7c9ca1bca92e1e10"
    sub: "5BE86359-073C-434B-AD2D-A3932222DABE"
    tenantId: "123456"
    
  3. Define custom identity resources (resources docs)

    I removed MyProfileService and added

    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        var customProfile = new IdentityResource(
                                name: "custom.profile",
                                displayName: "Custom profile",
                                claimTypes: new[] {
                                    "name",
                                    "given_name",
                                    "family_name",
                                    "email",
                                    "email_verified",
                                    "website",
                                    "address",
                                    "status",
                                    "tenantid" });
    
    return new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        customProfile
    };
    }
    

    Result in client I do not see all the claim types

    profile:
    amr: ["pwd"]
    auth_time: 1553026892
    family_name: "FamilyName"
    given_name: "Scott givenName"
    idp: "local"
    name: "Scott name"
    sid: "47ae7f9b5240742e2b2b94a739bed5fa"
    sub: "5BE86359-073C-434B-AD2D-A3932222DABE"
    website: "http://scott.com"
    

Upvotes: 3

Views: 824

Answers (1)

user4864425
user4864425

Reputation:

The problem is that you've removed default behaviour. So you'll need to restore that by adding the following line to your profile service (which is present in the DefaultProfileService):

context.AddRequestedClaims(context.Subject.Claims);

But it is not necessary to implement your own IProfileService. In this case you can suffice by configuring the scope for the client:

AllowedScopes = new List<string>
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "tenant.info",
        "api1"
    },

And requesting the scope in the client:

options.Scope.Add("tenant.info");

This should be enough to include the tenantId claim.

Upvotes: 3

Related Questions