Skip to content
This repository has been archived by the owner on Sep 18, 2021. It is now read-only.

UserInfo is not returning any Claims #1938

Closed
jerbersoft opened this issue Sep 23, 2015 · 6 comments
Closed

UserInfo is not returning any Claims #1938

jerbersoft opened this issue Sep 23, 2015 · 6 comments
Labels

Comments

@jerbersoft
Copy link

Context:

  • I'm using Hybrid Flow
  • Using Cookies authentication in the client.
  • Everything works fine. Like I can log in to the Idsrv3.
  • Client and Scope are InMemory
  • UserService is used and is inheriting from UserServiceBase

So here are the sample codes for the UserService I have created.

First, the code for the AuthenticateLocalAsync being overriden because users are logging in via Idsrv3's login page:

public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
        {
            var user = _facade.Get(context.UserName);
            if (user == null)
                return Task.FromResult(0);

            var isPasswordCorrect = BCrypt.Net.BCrypt.Verify(context.Password, user.Password);
            if (isPasswordCorrect)
            {
                context.AuthenticateResult = new AuthenticateResult(user.Id.ToString(), user.Username);
            }

            return Task.FromResult(0);
        }

Then next, I have implemented the GetProfileDataAsync:

public override Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            var identity = new ClaimsIdentity();

            UserInfo user = null;
            if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
                user = _facade.Get(context.Subject.Identity.Name);
            else
            {
                // get the sub claim
                var claim = context.Subject.FindFirst(item => item.Type == "sub");
                if (claim != null)
                {
                    Guid userId = new Guid(claim.Value);
                    user = _facade.Get(userId);
                }
            }

            if (user != null)
            {
                identity.AddClaims(new[]
                {
                    new Claim(Constants.ClaimTypes.PreferredUserName, user.Username),
                    new Claim(Constants.ClaimTypes.Email, user.EmailAddress)
                });
            }

            return Task.FromResult(identity.Claims);
        }

The code above shows that preferred_username and email claims were added to the identity.Claims and were returned. Also, both claims are in the requested claims collection.

Then in the client config/setup, I have this:

app.UseCookieAuthentication(new CookieAuthenticationOptions()
            {
                AuthenticationType = "Cookies"
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
            {
                Authority = "https://id.localhost.com",
                ClientId = "admin",
                RedirectUri = "https://admin.localhost.com/",
                PostLogoutRedirectUri = "https://admin.localhost.com/",
                ResponseType = "code id_token token",
                Scope = "openid profile email roles offline_access",
                ClientSecret = "admin",
                SignInAsAuthenticationType = "Cookies",
                Notifications = new OpenIdConnectAuthenticationNotifications()
                {
                    AuthorizationCodeReceived = async n =>
                    {
                        var identity = n.AuthenticationTicket.Identity;

                        var nIdentity = new ClaimsIdentity(identity.AuthenticationType, "email", "role");

                        var userInfoClient = new UserInfoClient(
                            new Uri("https://id.localhost.com/connect/userinfo"),
                            n.ProtocolMessage.AccessToken);

                        var userInfo = await userInfoClient.GetAsync();
                        userInfo.Claims.ToList().ForEach(x =>
                            nIdentity.AddClaim(new Claim(x.Item1, x.Item2)));

                      // some logic for adding claims
                    }
                }
});

Now, when I try to login and debug setting a breakpoint in the line after "var userInfo = await userInfoClient.GetAsync();", the user info returned does not have ANY claims. Not even the JsonObject or the Raw have values. (see screenshot below)

screen shot 2015-09-23 at 10 33 39 pm

I am quite confused because when I tried just using the default InMemoryUser and default user service, the user info does return claims correctly. I must be missing something.

Let me know if you need more context, I will supply them.

@leastprivilege
Copy link
Member

that's weird - check the logs.

@jerbersoft
Copy link
Author

Here's a part of the logs when getting the userinfo:

2015-09-24 06:48:37.191 -04:00 [Information] Creating Hybrid Flow response.
2015-09-24 06:48:37.201 -04:00 [Information] Creating Implicit Flow response.
2015-09-24 06:48:37.308 -04:00 [Information] Getting claims for identity token for subject: a404a765-4f3a-4ea8-a343-b1af67472deb
2015-09-24 06:48:37.384 -04:00 [Information] Posting to https://admin.localhost.com/
2015-09-24 06:48:41.157 -04:00 [Information] Start discovery request
2015-09-24 06:48:41.170 -04:00 [Information] Start key discovery request
2015-09-24 06:48:41.244 -04:00 [Information] Start userinfo request
2015-09-24 06:48:41.246 -04:00 [Information] Token found: AuthorizationHeader
2015-09-24 06:48:41.249 -04:00 [Information] Start access token validation
2015-09-24 06:48:41.317 -04:00 [Information] "Token validation success"
"{
  \"ValidateLifetime\": true,
  \"AccessTokenType\": \"Jwt\",
  \"ExpectedScope\": \"openid\",
  \"Claims\": {
    \"client_id\": \"admin\",
    \"scope\": [
      \"openid\",
      \"profile\",
      \"email\",
      \"roles\",
      \"offline_access\"
    ],
    \"sub\": \"a404a765-4f3a-4ea8-a343-b1af67472deb\",
    \"amr\": \"password\",
    \"auth_time\": \"1443091709\",
    \"idp\": \"idsrv\",
    \"iss\": \"https://id.localhost.com\",
    \"aud\": \"https://id.localhost.com/resources\",
    \"exp\": \"1443095317\",
    \"nbf\": \"1443091717\"
  }
}"
2015-09-24 06:48:41.322 -04:00 [Information] Creating userinfo response
2015-09-24 06:48:41.325 -04:00 [Information] Scopes in access token: "openid profile email roles offline_access"
2015-09-24 06:48:41.325 -04:00 [Information] Requested claim types: "sub name family_name given_name middle_name nickname preferred_username profile picture website gender birthdate zoneinfo locale updated_at email email_verified role"
2015-09-24 06:48:41.334 -04:00 [Information] Profile service returned to the following claim types: ""
2015-09-24 06:48:41.334 -04:00 [Information] End userinfo request
2015-09-24 06:48:41.338 -04:00 [Information] Returning userinfo response.

I don't see any suspects aside from the line below which I don't exactly know what it means.:

2015-09-24 06:48:41.334 -04:00 [Information] Profile service returned to the following claim types: ""

Also, to add context, here are the entries at the beginning of the logs when I restarted idsrv3:

2015-09-24 06:48:21.569 -04:00 [Information] Dependency injection container setup completed.
2015-09-24 06:48:21.681 -04:00 [Warning] AuthorizationCodeStore not configured - falling back to InMemory
2015-09-24 06:48:21.681 -04:00 [Warning] TokenHandleStore not configured - falling back to InMemory
2015-09-24 06:48:21.681 -04:00 [Warning] ConsentStore not configured - falling back to InMemory
2015-09-24 06:48:21.681 -04:00 [Warning] RefreshTokenStore not configured - falling back to InMemory

Some warnings. At the first look at it, I assume I need to implement something aside from the custom UserService (inheriting from UserServiceBase)?

@leastprivilege
Copy link
Member

so have you debugged the user service that you are actually returning the right claims in the GetProfileDataAsnyc?

@jerbersoft
Copy link
Author

Sorry about the late response. Let me check that out some more and I'll post my findings here.

@jerbersoft
Copy link
Author

Finally found the solution. I am using DefaultClaimsProvider in my setup because I just implemented a custom User Service. Now, in my GetProfileDataAsync implementation:

public override Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            var identity = new ClaimsIdentity();

            UserInfo user = null;
            if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
                user = _facade.Get(context.Subject.Identity.Name);
            else
            {
                // get the sub claim
                var claim = context.Subject.FindFirst(item => item.Type == "sub");
                if (claim != null)
                {
                    Guid userId = new Guid(claim.Value);
                    user = _facade.Get(userId);
                }
            }

            if (user != null)
            {
                identity.AddClaims(new[]
                {
                    new Claim(Constants.ClaimTypes.PreferredUserName, user.Username),
                    new Claim(Constants.ClaimTypes.Email, user.EmailAddress)
                });
            }

            return Task.FromResult(identity.Claims);
        }

You'll notice that I just returned a Task.FromResult(identity.Claims). The only missing line of code I needed is:

context.IssuedClaims = identity.Claims;    // before the return line

The reason behind this is because in the DefaultClaimsProvider.GetIdentityTokenClaimsAsync(), this is how it gets the claims from the GetProfileDataAsync.

await _users.GetProfileDataAsync(context);
var claims = FilterProtocolClaims(context.IssuedClaims);

Notice how context.IssuedClaims is passed as a parameter to the FilterProtocolClaims. So in practice, the context.IssuesClaims should be populated with the claims from the GetProfileDataAsync.

So, here's my updated GetProfileDataAsync. Just 1 line of code added!

public override Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            var identity = new ClaimsIdentity();

            UserInfo user = null;
            if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
                user = _facade.Get(context.Subject.Identity.Name);
            else
            {
                // get the sub claim
                var claim = context.Subject.FindFirst(item => item.Type == "sub");
                if (claim != null)
                {
                    Guid userId = new Guid(claim.Value);
                    user = _facade.Get(userId);
                }
            }

            if (user != null)
            {
                identity.AddClaims(new[]
                {
                    new Claim(Constants.ClaimTypes.PreferredUserName, user.Username),
                    new Claim(Constants.ClaimTypes.Email, user.EmailAddress)
                });
            }

context.IssuedClaims = identity.Claims; // <--- THIS IS THE MISSING PIECE
            return Task.FromResult(identity.Claims);
        }

Thanks @leastprivilege for pointing me to the right direction.

@2628377966
Copy link

thank you very much ,i also meet the same question that all claims missing ,but through your answer i found the reason .

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants