I've red the documentation, checked the samples but I couldn't find any detailed info about ProfileDataRequestContext.RequestedClaimTypes in IProfileServiceimplementation. I've tried different client/scope/resource combinations but context.ProfileDataRequestContext is always empty. When does this property contains data?
Whenever you request a scope that has associated claims.
Hi,
I know my post is quite late, but I m running in the same issue and all the info on the internet are not helping me much. I am probably missing out something, here is my current code:
Identity & resources
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API"),
new ApiResource
{
Name = "organisation",
UserClaims =
{
"workspace"
},
Scopes =
{
new Scope
{
Name = "orga"
}
}
}
};
}
Then I have my client configuration as follow:
// MVC client
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"organisation",
"orga",
"api1"
},
AllowOfflineAccess = true,
AlwaysIncludeUserClaimsInIdToken = true,
UpdateAccessTokenClaimsOnRefresh = true
},
// SPA Client
new Client
{
ClientId = "js",
ClientName = "JavaScript Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
AlwaysIncludeUserClaimsInIdToken = true,
UpdateAccessTokenClaimsOnRefresh = true,
RedirectUris = { "http://localhost:5003/callback.html" },
PostLogoutRedirectUris = { "http://localhost:5003/index.html" },
AllowedCorsOrigins = { "http://localhost:5003" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"organisation",
"orga",
"api1"
}
}
and in my Startup.cs I have:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
services.AddTransient<IProfileService, ProfileService>();
services.AddMvc();
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
//.AddTestUsers(Config.GetUsers());
.AddProfileService<ProfileService>();
services.AddAuthentication()
.AddOpenIdConnect("oidc", "OpenID Connect", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "https://demo.identityserver.io/";
options.ClientId = "implicit";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
}
Finally, I have IProfileService implemented as follow
public class ProfileService : IProfileService
{
private IUserLogic _userLogic;
public ProfileService(IUserLogic userLogic)
{
_userLogic = userLogic;
}
//Get user profile date in terms of claims when calling /connect/userinfo
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{
//depending on the scope accessing the user data.
if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
{
//get user from db (in my case this is by email)
var user = await _userLogic.GetByEmail(context.Subject.Identity.Name);
if (user != null)
{
var claims = GetUserClaims(user);
//set issued claims to return
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
else
{
//where and subject was set to my user id.
var userEmail = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");
if (!string.IsNullOrEmpty(userEmail?.Value))
{
//get user from db (find user by user id)
var user = await _userLogic.GetByEmail(userEmail.Value);
// issue the claims for the user
if (user != null)
{
var claims = GetUserClaims(user);
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
}
}
catch (Exception ex)
{
//log your error
}
}
//check if user account is active.
public async Task IsActiveAsync(IsActiveContext context)
{
//[...]
}
public static Claim[] GetUserClaims(User user)
{
return new Claim[]
{
new Claim("user_id", user.Id.ToString()),//("user_id", user.UserId.ToString() ?? ""),
new Claim(JwtClaimTypes.Name, $"{user.FirstName} {user.LastName}"),//(!string.IsNullOrEmpty(user.Firstname) && !string.IsNullOrEmpty(user.Lastname)) ? (user.Firstname + " " + user.Lastname) : ""),
new Claim(JwtClaimTypes.Email, user.Email),
new Claim("workspace", "wx|wz")
};
}
}
So now, according to my understanding of the doc, the line context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList(); should not be empty, and more specifically, context.RequestedClaimTypes should contains "workspace"
I think I'm missing a pretty important point, but I cannot gasp which one.
I have the same issue with empty RequestedClaimTypes, can you please reopen the ticket?
Is it expected that RequestedClaimTypes is filled with the ApiResource.claimTypes, instead of the associated claims to the requested scopes.
Any updates ?
I have the same issue. The RequestedClaimTypes is always empty.
You could try:
Check the properies of the context . There can be different content for "caller"
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Most helpful comment
Whenever you request a scope that has associated claims.