runtime: System.Security.Claims.Claim value type incorrect

The type of System.Security.Claims.Claim.Value is to string which is contrary to the spec:

Claim Value The value portion of a claim representation. A Claim Value can be any JSON value.

This is big problem for us while creating a .NET Core app for a large established ecosystem where JWTs have object claims.

The problem is exacerbated by the fact that now System.IdentityModel.Tokens.Jwt library is dependent on this implementation in the way it deserialises JWTs into ClaimsIdentity and then into ClaimsPrincipal… (e.g. here or here)

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 25 (18 by maintainers)

Most upvoted comments

As far I understand the correct way of processing a raw JWT into a principal is by executing this:

JwtSecurityTokenHandler handler;
var principal = handler.ValidateToken(rawJwt, tokenValidationParameters, out validatedToken);

Here’s an example JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiIsImlzRW5jcnlwdGVkIjoiRmFsc2UiLCJ4NXQiOiI1M0VENjE1NTUwNTlBRDg3QUE4MkNBNTYwRTQ4QkIxMkM1MzdGOUY1IiwidmVyIjoiMi4xIn0.eyJhdWQiOiJIZWxpeC5TZWN1cml0eS5VdGlsIiwiaWF0IjoiMTQ3MjA5NjU1Ny43NDM3NiIsIm5iZiI6IjE0NzIwOTY1NDQuNzU3NTkiLCJDbGFpbVNldHMiOlt7IkNsYWltcyI6eyJzZXJ2ZXJJZCI6IkhlbGl4LkNvbnRhaW5lcnMuRGV2Iiwic2VydmVyVmVyc2lvbiI6ImRldiIsImlzc1g1dCI6IjUzRUQ2MTU1NTA1OUFEODdBQTgyQ0E1NjBFNDhCQjEyQzUzN0Y5RjUifSwiUHJvdmlkZXIiOiJIZWxpeC5Db250YWluZXJzLkRldi52ZGV2IiwiU2NoZW1hIjoiSGVsaXguQ29udGFpbmVyIiwiVmVyc2lvbiI6IlYxIn1dLCJpc3MiOiJIZWxpeC5Db250YWluZXJzLkRldi52ZGV2IiwiZXhwIjoiMTQ3MjA5Nzc0NC43NTg1OSIsInNzaWQiOiJjNmJkNzY3ZjE4YWU0ZTQyOTliMmY4YjJmNzhmODU1NSJ9.W8ARsO3IKMO_CBl5fMkgTEkPmoZZvjaX46-mmVHqT5hQAbQVBmnc18B9VxsSS34YKVE2dBQwZHjhu2ROSOCKeuHOqHjjS_HuSdDLOdi7rJUdpKw1GE-lBqxzUPojAlUvLRlq7KjbwipXd7bJyMk7chVU9r548pmljDAlm7SOqmM-qcZ8X0sgQcDxxZoacJiL9xQpbJPi9CVHC_ms2LJhm6AFcCNTlRZNgAmMvoIBWfjXVsVC92HFgqd_qTpMvudTs216LIfslpJC0WiU4SFWKV2Bt5rGGVVqSe4vXb4W1Si58t8ORcepRnkZ1jkEuKf2VpHTEw0ylwX_BLqnnKdavQ

Decoded to JSON:

{
  "typ": "JWT",
  "alg": "RS512",
  "isEncrypted": "False",
  "x5t": "53ED61555059AD87AA82CA560E48BB12C537F9F5",
  "ver": "2.1"
}
{
  "aud": "Helix.Security.Util",
  "iat": "1472096557.74376",
  "nbf": "1472096544.75759",
  "ClaimSets": [
    {
      "Claims": {
        "serverId": "Helix.Containers.Dev",
        "serverVersion": "dev",
        "issX5t": "53ED61555059AD87AA82CA560E48BB12C537F9F5"
      },
      "Provider": "Helix.Containers.Dev.vdev",
      "Schema": "Helix.Container",
      "Version": "V1"
    }
  ],
  "iss": "Helix.Containers.Dev.vdev",
  "exp": "1472097744.75859",
  "ssid": "c6bd767f18ae4e4299b2f8b2f78f8555"
}

Once raw JWT is loaded into an instance of JwtSecurityToken it has the following Payload: image

However its Claim are as follows: image

The ClaimSets claim is serialised back to string…

Now a JwtSecurityTokenHandler takes the deserialised JWT and loads it into an instance of ClaimsIdentity:

foreach (Claim jwtClaim in jwt.Claims)
{
    // snip ...            
    Claim c = new Claim(claimType, jwtClaim.Value, jwtClaim.ValueType, issuer, issuer, identity);
    // snip ...            
    identity.AddClaim(c);
}

At the end of all of this, the principal has all claims serialised back to string which is incorrect and extremely inefficient image

One of the primary reasons is that we need to serialize an identity into a cookie. We can’t serialize and deserialize arbitrary types. There are also several other operations we do like merging multiple ClaimsPrincipals into a single principal.

@RussKie The last two sentences were related to asp.net users. For users of IdentityModel, we have extensibility that allows you to create your own custom ClaimsIdentity returned from ValidateToken. Both JwtSecurityTokenHandler.CreateClaimsIdentity and TokenValidationParameters.CreateClaimsIdentity are virtual and between the two, you can have a custom ClaimsIdentity OR CustomClaims. We do not have a delegate for CreateClaimsIdentity, as we do for many other overloads. Deriving is necessary.

At the ClaimsIdentity level, all values are strings.

I am the only one seeing the problem with this? 😱

As an end-consumer app developer I should not be locked in dealing with strings, it is just too impractical. I should be dealing with whatever objects fit my business requirements. Ultimately in the code the developers don’t deal with JWTs (it is a mere transport object), they deal the identities.

To put this in a context. Previously HTTP context would expose the user as IPrincipal (https://github.com/Microsoft/referencesource/blob/master/System.Web/HttpContext.cs#L1237). Now in OWIN/.NETCore HttpContext it is no longer the case, it is ClaimsPrincipal. This means I can’t substitute it with a custom principal. Because I can’t put my custom principal I can’t expose my custom identity as it is now of ClaimsIdentity type. And the ClaimsIdentity only provides me with strings…

For example:

public interface IMyIdentity : IIdentity 
{
    IdentityClaimSetCollection ClaimSets { get; set; }
}

public class MyIdentity : IMyIdentity 
{
    IdentityClaimSetCollection ClaimSets { get; set; }
}

public class MyPrincipal : IPrincipal
{
    public MyIdentity Identity { get; set; }
    IIdentity IPrincipal.Identity
    {
        get { return Identity; }
    }
}

HttpContext.Current.User = new MyPrincipal();

I can no longer do anything like this in OwinContext / .NETCore HttpContext…

I don’t agree with the current implementation of ClaimsIdentity - it is too rigid. Either it has to support more than just string claims or there has to be a mechanism for me to inject my custom principal/identity which support other than string claims. So far I haven’t found any ways to achieve this cleanly. 😢