I've finally been able to get an access token from IdentityServer, and use it to retrieve the UserInfo.
However it seems that the access token I'm getting from IdentityServer only contains the sub
claim.
Here is an example token
eyJhbGciOiJSUzI1NiIsImtpZCI6IjVCOTBDN0JBNkExMjI2RjEyMEU0QzJGOEQzMjIwMzAxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MzcwNTI5MDIsImV4cCI6MTYzNzEzOTMwMiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNTkiLCJhdWQiOiJ3ZWF0aGVyZm9yZWNhc3RzIiwiY2xpZW50X2lkIjoiU3NvQXBwbGljYXRpb25DbGllbnQiLCJjZW50cmFsLXRoZWNsaWVudCI6IlRoZSBTU08gY2xpZW50Iiwic3ViIjoiYTUxYjBkZWMtODQyYS00ZWMyLTgwMGEtMzRmYWQyNTRjZTBlIiwiYXV0aF90aW1lIjoxNjM3MDUyMDcyLCJpZHAiOiJsb2NhbCIsImp0aSI6IkQ3Rjc2MzgwQzNERDkzMzVERDVFMTE1NjE4MDkzNEUwIiwic2lkIjoiOEY1OUZFMjRDNkY1NTM2ODhEQzVCMTM5QjNFQUU1MTMiLCJpYXQiOjE2MzcwNTI5MDIsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJlbWFpbCIsIndlYXRoZXJmb3JlY2FzdHMucmVhZCIsIndlYXRoZXJmb3JlY2FzdHMud3JpdGUiXSwiYW1yIjpbInB3ZCJdfQ.MiDmgc7AzbVpogbp8ID3WvJ0eo4a30_taxR9EI9ylyJASSdOiNSsk-sGuW-YnJIzf668EQGkTym6FMIvOyTxem9DxxIs8nI_rboHLvuvj4e7CtJeELwbZyraZtAxjVjm9tn0BVRZxuskzb6XSq4xGrt2ag_E0Re5MeQOjtyL0EeMS5md5IEywfD7ThH7pIu8SofFvV5tAYbwO-OPd5YyqpPGKXslRtFlyc7lj9faQh-e2CRMql5rSwhJRqCiaIaLxvXk8ZwISfdhmuyzHA88xrzXkqTK_RElhq4PY_GqpRe64nMvIBrkSeoOGLzlQNE9wa58UypZFFV4l8Cpy3_P2Q
Here is the decoded token
I should be able to use this token to call the /connect/userinfo
endpoint
And I can see this in the Output window:
IdentityServer4.Hosting.EndpointRouter: Debug: Request path /connect/userinfo matched to endpoint type Userinfo
IdentityServer4.Hosting.EndpointRouter: Debug: Endpoint enabled: Userinfo, successfully created handler: IdentityServer4.Endpoints.UserInfoEndpoint
IdentityServer4.Hosting.IdentityServerMiddleware: Information: Invoking IdentityServer endpoint: IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo
IdentityServer4.Endpoints.UserInfoEndpoint: Debug: Start userinfo request
IdentityServer4.Validation.BearerTokenUsageValidator: Debug: Bearer token found in header
IdentityServer4.Endpoints.UserInfoEndpoint: Trace: Calling into userinfo request validator: IdentityServer4.Validation.UserInfoRequestValidator
IdentityServer4.Validation.TokenValidator: Trace: Start access token validation
IdentityServer4.EntityFramework.Stores.ClientStore: Debug: SsoApplicationClient found in database: True
IdentityServer4.Stores.ValidatingClientStore: Trace: Calling into client configuration validator: IdentityServer4.Validation.DefaultClientConfigurationValidator
IdentityServer4.Stores.ValidatingClientStore: Debug: client configuration validation for client SsoApplicationClient succeeded.
IdentityServer4.EntityFramework.Stores.ClientStore: Debug: SsoApplicationClient found in database: True
IdentityServer4.Stores.ValidatingClientStore: Trace: Calling into client configuration validator: IdentityServer4.Validation.DefaultClientConfigurationValidator
IdentityServer4.Stores.ValidatingClientStore: Debug: client configuration validation for client SsoApplicationClient succeeded.
IdentityServer4.Validation.TokenValidator: Debug: Calling into custom token validator: IdentityServer4.Validation.DefaultCustomTokenValidator
IdentityServer4.Validation.TokenValidator: Debug: Token validation success
{
"ValidateLifetime": true,
"AccessTokenType": "Jwt",
"ExpectedScope": "openid",
"JwtId": "2FB8F8A941528DAF18D8C523BCC9A770",
"Claims": {
"nbf": 1637062004,
"exp": 1637148404,
"iss": "https://localhost:44359",
"aud": "weatherforecasts",
"client_id": "SsoApplicationClient",
"central-theclient": "The SSO client",
"sub": "959c9bfa-ed30-4638-9986-63cf1589eff8",
"auth_time": 1637060908,
"idp": "local",
"jti": "2FB8F8A941528DAF18D8C523BCC9A770",
"sid": "75C294FC15A544FE60E361B495EE5BCA",
"iat": 1637062004,
"scope": [
"openid",
"profile",
"email",
"weatherforecasts.read",
"weatherforecasts.write"
],
"amr": "pwd"
}
}
IdentityServer4.Endpoints.UserInfoEndpoint: Trace: Calling into userinfo response generator: IdentityServer4.ResponseHandling.UserInfoResponseGenerator
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Creating userinfo response
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Scopes in access token: openid profile email weatherforecasts.read weatherforecasts.write
IdentityServer4.EntityFramework.Stores.ResourceStore: Debug: Found openid, profile, email identity scopes in database
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Requested claim types:
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Information: Profile service returned the following claim types:
IdentityServer4.Endpoints.UserInfoEndpoint: Debug: End userinfo request
You can even see my custom dedicated client claims being appended during the token validation. But yet only the sub claim is returned from the identityserver userinfo endpoint...
How can I fix this?
I added the ProfileService
, and it's being called. But as Dejan already mentioned, RequestedClaimTypes
is empty. However I updated the database by adding
[{"type":"email","identityResourceId":1003},{"type":"sub","identityResourceId":1003}]
records to the IdentityResourceClaim
table. In the output window I can now see that the email
and sub
claims are requested:
IdentityServer4.Validation.TokenValidator: Debug: Token validation success
{
"ValidateLifetime": true,
"AccessTokenType": "Jwt",
"ExpectedScope": "openid",
"JwtId": "C37AF164BF3A7DE6A28FA7538683248F",
"Claims": {
"nbf": 1637069285,
"exp": 1637155685,
"iss": "https://localhost:44359",
"aud": "weatherforecasts",
"client_id": "SsoApplicationClient",
"central-theclient": "The SSO client",
"sub": "959c9bfa-ed30-4638-9986-63cf1589eff8",
"auth_time": 1637067659,
"idp": "local",
"email": "pieterjan@example.com",
"name": "Pieterjan",
"jti": "C37AF164BF3A7DE6A28FA7538683248F",
"sid": "BBFA9FD0A06824FA4E982DB1D3669A86",
"iat": 1637069285,
"scope": [
"openid",
"profile",
"email",
"weatherforecasts.read",
"weatherforecasts.write"
],
"amr": "pwd"
}
}
IdentityServer4.Endpoints.UserInfoEndpoint: Trace: Calling into userinfo response generator: IdentityServer4.ResponseHandling.UserInfoResponseGenerator
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Creating userinfo response
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Scopes in access token: openid profile email weatherforecasts.read weatherforecasts.write
IdentityServer4.EntityFramework.Stores.ResourceStore: Debug: Found openid, profile, email identity scopes in database
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Requested claim types: email sub
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Information: Profile service returned the following claim types: email name
IdentityServer4.Endpoints.UserInfoEndpoint: Debug: End userinfo request
IdentityServer4.Hosting.IdentityServerMiddleware: Trace: Invoking result: IdentityServer4.Endpoints.Results.UserInfoResult
The code finally enters the ExternalLoginCallback
, but the await signInManager.GetExternalLoginInfoAsync()
returns null now.
The response from https://localhost:44359/connect/userinfo
is
{
"email": "pieterjan@example.com",
"name": "Pieterjan",
"sub": "959c9bfa-ed30-4638-9986-63cf1589eff8"
}
The userinfo endpoint calls GetProfileDataAsync
from IdentityServer4.Services.IProfileService
to obtain the requested claim values. So the simple solution would be to implement that service.
Assuming you have your user manager defined as IMyUserManager
and that it has the methods referenced here (GetClaimsForUser
and IsActive
), the simplified implementation might look like this:
public class MyProfileService : IProfileService
{
private readonly IMyUserManager userManager;
public MyProfileService(IMyUserManager usermanager)
{
this.userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// context.RequestedClaimTypes will contain the claims you requested when invoking the token endpoint
var myClaims = await userManager.GetClaimsForUser(context.Subject, context.RequestedClaimTypes);
context.IssuedClaims = myClaims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = await userManager.IsActive(context.Subject);
}
}
You would then need to configure IdentityServer4 to use this implementation, by calling AddProfileService
at startup like this:
services.AddIdentityServer()
// ...
.AddProfileService<MyProfileService>();
I should also add that, in order for your wanted claim to be present in context.RequestedClaimTypes
, it needs to be associated with the scopes you directly request when making a call to token endpoint.
At startup, when configuring IdentityServer4, make sure the identity resources you supplied to your AddIdentityResources
call contains the required claim.