Reputation: 2991
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
Without MyProfileService registered
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
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
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"
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
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