Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot cast Newtonsoft.Json.Linq.JArray to Newtonsoft.Json.Linq.JToken #4669

Closed
farshid3003 opened this issue Aug 23, 2017 · 24 comments
Closed
Labels
area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer bug This issue describes a behavior which is not expected - a bug.

Comments

@farshid3003
Copy link

I use Identityserver4 and try to connect with a .ner core 2.0 mvc app to Open Id authentication
My Configuration is here

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie()
            .AddOpenIdConnect(options =>
            {
                options.ClientId = "clientId";
                options.ClientSecret = "secret";
                options.Authority = Configuration["IdentityServerAddress"];
                options.SignedOutRedirectUri = Configuration["IdentityServerManagerAddress"];
                options.ResponseType = "code id_token";
                options.Scope.Add("openid");
                options.Scope.Add("roles");
                options.Scope.Add("profile");
                options.Scope.Add("offline_access");
                options.Scope.Add("phone_number"); 
                options.SignInScheme = "Cookies";
                options.GetClaimsFromUserInfoEndpoint = true;
                options.SaveTokens = true;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "name",
                    RoleClaimType = "role"
                };
            });

but I got this error :

InvalidCastException: Cannot cast Newtonsoft.Json.Linq.JArray to Newtonsoft.Json.Linq.JToken.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler+<HandleRequestAsync>d__12.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware+<Invoke>d__6.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware+<Invoke>d__7.MoveNext()

@Tratcher
Copy link
Member

That's odd. Did it work in 1.1?

Side note: there's a rethrow here that's dropping the original stack trace https://github.com/aspnet/Security/blob/5b29bced0d2f1cfc78843bcd9ec9a828c6fb5fef/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs#L98

Can you hook into the OnRemoteFailure event and get the original stack trace?

@farshid3003
Copy link
Author

Yes it works in 1.1 and now I use core 1.1
I tried to upgrade and I could not

@farshid3003
Copy link
Author

   at Newtonsoft.Json.Linq.Extensions.Convert[T,U](T token)
   at Newtonsoft.Json.Linq.JToken.Value[T](Object key)
   at Microsoft.AspNetCore.Authentication.OpenIdConnect.Claims.UniqueJsonKeyClaimAction.Run(JObject userData, ClaimsIdentity identity, String issuer)
   at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.<GetUserInformationAsync>d__24.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.<HandleRemoteAuthenticateAsync>d__21.MoveNext()

@Tratcher
Copy link
Member

That's more interesting. break it under the debugger and see which claim it is. It's getting back a list when it's only expecting a single item. Sharing a sanitized dump of the user-info http response would be helpful as well. Fiddler can help you capture that.

@farshid3003
Copy link
Author

I think there is no user info and it throw before sending request. the problem should be on parsing the token


POST http://localhost:30995/signin-oidc HTTP/1.1
Host: localhost:30995
Connection: keep-alive
Content-Length: 1551
Cache-Control: max-age=0
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8,fa;q=0.6,tr;q=0.4
Cookie: .AspNetCore.OpenIdConnect.Nonce.CfDJ8ExhJTS9lfdBoe3OrgtceR9iopMGjToVNaQA7lUVzolJaIy1AajCoMoDcZKMaWCx1A33jX_6QF3D6IVfnoqImTq4dhauZiwsdRV65TgDa3D-7eYoypUCj66-frC7BE01r7gyf1TV_f3EqXxbelLxozWDozprOXTdC3GHcqp-Ls7E9aP60Z4fnqTSJdeYr8ETQqeFlHxaTlffVgOAcEaqZlMgnx9hDYrDSAVJTrG4NUCuFQZ8Brs3Yy3-kJ5GS2lqT0yOiUE33o-Aa71CU5z5beY=N; .AspNetCore.Correlation.OpenIdConnect.XjlaTsr2pQUN-xeM2E62qmgIBSbix7xX5dVmKYvcwXk=N; .AspNetCore.OpenIdConnect.Nonce.CfDJ8ExhJTS9lfdBoe3OrgtceR-cPwSMEvdixHSrl8YiUUFpqiuh4zHJHlZyyEwBFHLRsE4_pCuDOtSOBDy8DrfhLhpNoZ2OsV4Qoloxd8FFATAtAty_47oir8iiTEN11G7dU9CLl9KHfWOvMtue4EgupyuUcXLQfRkXHCaoim7aNSDtR5oVEGkVVGRhMlkZNSxMIntNXcMZ7GhpQVpnZPGJx5HAzrrkBganOQEekkhWgyUcHV-0JkRYrjJxaCD3P4Icna8IKfNbFiGSW-i9kdbPj1g=N; .AspNetCore.Correlation.OpenIdConnect.JytGX4KaDl2xqLzqQCEcLiZh4sy8tsEajPTiMGlj1BY=N

code=e71799f2c366f43dee44c4661eaafc083fba9016c8c4af1bdd5df789b396beaa&id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRFNjkzMTYzRkRCMTU2RjdDQTgwMDI1RkI3M0U2NjZFRjk1MjgxRjUiLCJ0eXAiOiJKV1QiLCJ4NXQiOiIzbWt4WV8yeFZ2ZktnQUpmdHo1bWJ2bFNnZlUifQ.eyJuYmYiOjE1MDM1MTk1MjMsImV4cCI6MTUwMzUyMjUyMywiaXNzIjoiaHR0cHM6Ly9hY2NvdW50Lm1vcmhpcG8uY29tIiwiYXVkIjoiTW9yaGlwb01hbmFnZXIiLCJub25jZSI6IjYzNjM5MTE2MzI5ODQ1ODE3NS5ZamRqTVRRellqTXRZekV5TnkwMFpqVTJMVGc1WkdFdE0yRmtaR0U1TURobE5tUmpOelptTVdZeU1UY3RZalF5TVMwME1ERXpMV0l4Wm1FdFpqUmpZMk5qTVRoaVlqUXgiLCJpYXQiOjE1MDM1MTk1MjMsImNfaGFzaCI6IlppS0ZBamJ5Z3ZjY3pSbUlZRVhwbFEiLCJzaWQiOiIwMDJmNTE5ZjkwM2QzODQxNDg1MGY5MDRmOWE3OWM2NCIsInN1YiI6IjEwIiwiYXV0aF90aW1lIjoxNTAzNTE5MDU4LCJpZHAiOiJsb2NhbCIsImFtciI6WyJwd2QiXX0.daPSQvWdA7knCYq5T2zhjuoWq2uZgi30ys9jRoGcdtC0rXIBqyXsRaR1Y7OZky1kSRbEOR0G6-_Jlx4Pm-TCGTzZGSQZyW0wLqgDlmaeZxGRGaC1liCJzt37M0MBqfsqxYREfAxecXIlGoKuoo5lKGdFOMeN3EBdH7hk8rw2fiKHGAk8nOHWBLcIhYqA1uEDa4UazWwU10DFis-baNSY0O4gcGoDAdQf0amA9fAlU1b0L5xhwSqix4Brn9pxtxQnbqNA11wD0_pqosoPb0vzNuRMF7hBHSAR9D8hrTYex4YpPvZAbNyk46g_UEQsQIk6JVvewLrmMClXXpWaESdxRQ&scope=openid+profile+roles+phone_number+offline_access&state=CfDJ8ExhJTS9lfdBoe3OrgtceR9H1TOFAb_fe04mPirlCa9HI06UbAeo3LgQdPU8iz8QDBGBtPxEkYE51qozrefMdh28JpcRtAtvzUyCw_IFJ8x7X1_3FF38UZiSk1Mx6a5Hpwk_t5BjModmL-gHYFbCTJFu9pj5HCv0PXNOBQUMAe49AkBhOXwCIFOKs-TX7hExU0nnWQjk5f51ytumItL56CR0i1I849IxvePba0-x6l0Sc3yMCGPlXVdx26QKIIAcGhdOfEw0iI58dcvdmz11p4ieJ7kKZdxUKJaKnULEk6tYWU5CSt-JuAckFKSb9dpCEA&session_state=i-IKOsYpgMABchSlENU2d96VzGRNoYDqL15oG699bPw.56fc0d50e717ebe358c9d7e01bfe78e7

@farshid3003
Copy link
Author

farshid3003 commented Aug 23, 2017

Sorry it was because of https here is dump

{
  "sub": "10",
  "name": [ "user.name", "User Name" ],
  "AspNet.Identity.SecurityStamp": "240dc561-37ff-4eb9-a58a-f760d1b9abaa",
  "role": [ "CSM_User", "dev", "admin", "SuperAdmin", "Admin", "LdapUser", "TranslationAdmin" ],
  "UnitCode": "3120002566",
  "JobTitle": "Kidemli Yazilim Uzmani",
  "JobPhone": "0212 999 9349",
  "given_name": "User",
  "family_name": "Name",
  "birthdate": "03/06/1978",
  "email": "user.name@company.com",
  "gender": "male"
}

@Tratcher
Copy link
Member

It looks like the name claim is the issue. Try oidcOptions.ClaimActions.Remove("name") to drop that mapping.

It looks like IdentityServer4 is using a non-standard name format.
http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
name | string | End-User's full name in displayable form including all name parts, possibly including titles and suffixes, ordered according to the End-User's locale and preferences.

@farshid3003
Copy link
Author

farshid3003 commented Aug 23, 2017

Yes you right the problem must be there how I missed it :) thank you
It is not the identityserver4 problem it is my problem that assign more than one name claim to the user
there is a name property on applicationUser and there is a name claim which I generate by first name and last name

@farshid3003
Copy link
Author

I talk with identityserver team and they ask, how said we cannot have more than one claim.
in your link it said which name must be an string and it is. but it is more than one same as role.

@Eilon
Copy link
Member

Eilon commented Aug 24, 2017

Ideally we'd have improved error handling when parsing these messages. We should detect that data types are incorrect (as well as other issues) and throw proper exceptions for those.

@leastprivilege
Copy link
Contributor

We should detect that data types are incorrect

Maybe you need more flexible parsing instead.

@Eilon
Copy link
Member

Eilon commented Aug 25, 2017

My understanding was that the data type must be a string per the protocol. If that's not correct, or I've misunderstood, I agree we need to consider making it more flexible.

@leastprivilege
Copy link
Contributor

I agree that multiple names are an edge case and not directly specified by the OIDC spec.

But this is just the general problem of translating JWTs/JSON to .NET claims. We've been through that, and just made our code a bit more "permissive".

@Eilon
Copy link
Member

Eilon commented Aug 25, 2017

@leastprivilege is it an edge case or is it invalid (per the spec)? @Tratcher in the discussion that we had it was mentioned that it's not valid - do you have a spec pointer about that?

@Tratcher
Copy link
Member

It's quoted above: https://github.com/aspnet/Security/issues/1383#issuecomment-324461357

@Eilon
Copy link
Member

Eilon commented Aug 28, 2017

Ah, sure, so if the spec says this field in particular must be a string, I think it's fine to not support any other behavior. We just need to see if we can make it be a better error behavior.

@mmillican
Copy link

FYI in my specific case it was family_name claim that was an array and not a string. It would be great if we could see a better error message related to this.

@opnarius
Copy link

opnarius commented Oct 27, 2017

I ran into this exception while connecting my application to IdentityServer4 with AzureAD as external authentication provider. My application is using Hybrid flow to connect to IdentityServer4. I get properly redirected to Azure, login, and code and id_tokens are properly issued. This exception is raised in my application when userInfo endpoint is invoked.

Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[17]
      Exception occurred while processing message.
System.InvalidCastException: Cannot cast Newtonsoft.Json.Linq.JArray to Newtonsoft.Json.Linq.JToken.
   at Newtonsoft.Json.Linq.Extensions.Convert[T,U](T token)
   at Newtonsoft.Json.Linq.JToken.Value[T](Object key)
   at Microsoft.AspNetCore.Authentication.OpenIdConnect.Claims.UniqueJsonKeyClaimAction.Run(JObject userData, ClaimsIdentity identity, String issuer)
   at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.<GetUserInformationAsync>d__24.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.<HandleRemoteAuthenticateAsync>d__21.MoveNext()

If I configure my application to use Implicit flow this exception is not observed.

In my case AzureAD returns 2 name claims, for the user I'm logging in with.

@DeanMachine
Copy link

Yep, thanks to this post, I figured out that is was the 'name' claim that was a string[] instead of a string.

I confirmed that AAD sends two name claims: one is issued by AAD provider the other is LOCAL AUTHORITY. Our identity server has always stored and passed on interesting claims like name, phone, etc. as part of the profile. Seems like the latest changes to .NET Core are enforcing the types for certain claim types.

I updated our identity server to not pass on the local authority issuer claims. The only one I ever see is for name. I tested with an AAD "visitor" id and a regular AD user. Now there is only one name claim and it will be the human friendly version issued by AAD.

@mstrong64
Copy link

In my case the problem relates to IDS4 returning the role claim as an array when the user has multiple roles (see http://docs.identityserver.io/en/release/endpoints/userinfo.html). This causes an exception when trying to get the role into the Principal via:

oidcOptions.ClaimActions.MapUniqueJsonKey(JwtClaimTypes.Role, JwtClaimTypes.Role);

I've got around this as follows:

oidcOptions.Events = new OpenIdConnectEvents()
{
  OnUserInformationReceived = async context =>
  {
    // IDS4 returns multiple claim values as JSON arrays, which break the authentication handler
    if (context.User.TryGetValue(JwtClaimTypes.Role, out JToken role))
    {
      var claims = new List<Claim>();
      if (role.Type != JTokenType.Array) {
        claims.Add(new Claim(JwtClaimTypes.Role, (string)role));
      }
      else  {
        foreach (var r in role)
          claims.Add(new Claim(JwtClaimTypes.Role, (string)r));
      }
      var id = context.Principal.Identity as ClaimsIdentity;
      id.AddClaims(claims);
    }
  ...
}

@jastBytes
Copy link

Thank you so much @mstrong64! You just saved me from getting completely insane here. 👍

@Eilon
Copy link
Member

Eilon commented Oct 11, 2018

Let's improve the error here to at least say what property didn't match what was expected per the spec (and maybe info about the mismatched types).

@aspnet-hello aspnet-hello transferred this issue from aspnet/Security Dec 13, 2018
@aspnet-hello aspnet-hello added this to the 3.0.0-preview2 milestone Dec 13, 2018
@aspnet-hello aspnet-hello added area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer bug This issue describes a behavior which is not expected - a bug. labels Dec 13, 2018
@Eilon
Copy link
Member

Eilon commented Dec 13, 2018

Closing because this change doesn't seem worth it.

@EricHerlitz
Copy link

EricHerlitz commented Jun 5, 2019

I had the same issue and like @mstrong64 suggested remapping some claims was the key. In my case, the name claim was the problem, since it is stored as a claim in IdentityServer4 there can be multiple instances of it

Cleaning that up and we were good to go

OnUserInformationReceived = async ctx =>
{
    var nameClaims = ctx.Principal.Claims.Where(x => x.Type.Equals(JwtClaimTypes.Name)).ToList();
    if (nameClaims.Count > 1)
    {
        var name = nameClaims.FirstOrDefault();
        ctx.User.Remove(JwtClaimTypes.Name);
        ctx.User.Add(JwtClaimTypes.Name, new JValue(name?.Value));
    }
},

Depending on your mapping JwtClaimTypes.Name can also be ClaimTypes.Name

@dotnet dotnet locked as resolved and limited conversation to collaborators Dec 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer bug This issue describes a behavior which is not expected - a bug.
Projects
None yet
Development

No branches or pull requests