3

From the IdentityServer 4 documentation : If the scopes requested are an identity resources, then the claims in the RequestedClaimTypes will be populated based on the user claim types defined in the IdentityResource

This is my identity resource:

return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Phone(),
                new IdentityResources.Email(),
                new IdentityResource(ScopeConstants.Roles, new List<string> { JwtClaimTypes.Role })
            };

and this is my client

AllowedScopes = {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.Phone,
                        IdentityServerConstants.StandardScopes.Email,
                        ScopeConstants.Roles
                    },

ProfileService - GetProfileDataAsync method:

public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var sub = context.Subject.GetSubjectId();
        var user = await _userManager.FindByIdAsync(sub);
        var principal = await _claimsFactory.CreateAsync(user);

        var claims = principal.Claims.ToList();
        claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();

        if (user.Configuration != null)
            claims.Add(new Claim(PropertyConstants.Configuration, user.Configuration));

        context.IssuedClaims = claims;
    }

principal.claims.ToList() has all the claims listed but context.RequestedClaimTypes is empty, hence the filter by context.RequestedClaimTypes.Contains(claim.Type)) returns no claims.

Client configuration:

let header = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' });

let params = new HttpParams()
    .append('username', userName)
    .append('password', password)
    .append('grant_type', 'password')
    .append('scope', 'email offline_access openid phone profile roles api_resource')
    .append('resource', window.location.origin)
    .append('client_id', 'test_spa');

let requestBody = params.toString();

return this.http.post<T>(this.loginUrl, requestBody, { headers: header });

Response type :

export interface LoginResponse {
    access_token: string;
    token_type: string;
    refresh_token: string;
    expires_in: number;
}

Someone indicated adding AlwaysIncludeUserClaimsInIdToken = true resolves the issue - I tried and it did not.

What am i missing here? Please help.

Ganu
  • 412
  • 1
  • 4
  • 12

1 Answers1

1

You're using resource owner password flow. This is an OAuth2 flow.

OAuth2 is not really suited for "identity data". Therefore a typical setup is to acquire an access token first (like you do), but then you'd use this token to call /userinfo endpoint, which would send back to you that user identity data.

Because of that, on the first request (getting an access token) in your ProfileService, RequestedClaimTypes will not have any claims related to identity resources (e.g. profile, email).

However, on the second call (/userinfo endpoint), your ProfileService would be called again (Caller=UserInfoEndpoint). and RequestedClaimTypes should now contain the identity claims you miss.

If you want to get identity data in a single call, then you should use an OpenID flow (e.g Implicit with response_type=id_token). You would then get an id token with this data right away (given AlwaysIncludeUserClaimsInIdToken was set to true). When your ProfileService is called (Client=ClaimsProviderIdentityToken), RequestedClaimTypes will contain identity claims.

Reference: https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/src/Services/Default/DefaultClaimsService.cs#L62