diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt index e69de29bb2..e9fb4de0f1 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt @@ -0,0 +1,2 @@ +virtual Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadHeaderValue(ref System.Text.Json.Utf8JsonReader reader, System.Collections.Generic.IDictionary claims) -> void + diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs index 72d052195b..c7e2872cfd 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs @@ -35,7 +35,7 @@ internal class JsonClaimSet internal JsonClaimSet() { - _jsonClaims = new Dictionary(); + _jsonClaims = []; } internal JsonClaimSet(Dictionary jsonClaims) @@ -88,9 +88,9 @@ internal static void CreateClaimFromObject(List claims, string claimType, claims.Add(new Claim(claimType, m.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Double, issuer, issuer)); else if (value is null) claims.Add(new Claim(claimType, string.Empty, JsonClaimValueTypes.JsonNull, issuer, issuer)); - else if (value is IList ilist) + else if (value is IList iList) { - foreach (var item in ilist) + foreach (var item in iList) CreateClaimFromObject(claims, claimType, item, issuer); } else if (value is JsonElement j) @@ -109,6 +109,10 @@ internal static void CreateClaimFromObject(List claims, string claimType, if (claim != null) claims.Add(claim); } + else + { + claims.Add(new Claim(claimType, value.ToString(), ClaimValueTypes.String, issuer, issuer)); + } } internal static Claim CreateClaimFromJsonElement(string claimType, string issuer, JsonElement jsonElement) diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs index 2a166298ab..28dc4bf4f2 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Text.Json; using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens.Json; namespace Microsoft.IdentityModel.JsonWebTokens @@ -41,41 +42,7 @@ internal JsonClaimSet CreateHeaderClaimSet(ReadOnlySpan byteSpan) { if (reader.TokenType == JsonTokenType.PropertyName) { - if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Alg)) - { - _alg = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Alg, ClassName, true); - claims[JwtHeaderParameterNames.Alg] = _alg; - } - else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Cty)) - { - _cty = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Cty, ClassName, true); - claims[JwtHeaderParameterNames.Cty] = _cty; - } - else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Kid)) - { - _kid = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Kid, ClassName, true); - claims[JwtHeaderParameterNames.Kid] = _kid; - } - else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Typ)) - { - _typ = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Typ, ClassName, true); - claims[JwtHeaderParameterNames.Typ] = _typ; - } - else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.X5t)) - { - _x5t = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.X5t, ClassName, true); - claims[JwtHeaderParameterNames.X5t] = _x5t; - } - else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Zip)) - { - _zip = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Zip, ClassName, true); - claims[JwtHeaderParameterNames.Zip] = _zip; - } - else - { - string propertyName = reader.GetString(); - claims[propertyName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, propertyName, JsonClaimSet.ClassName, true); - } + ReadHeaderValue(ref reader, claims); } // We read a JsonTokenType.StartObject above, exiting and positioning reader at next token. else if (JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.EndObject, false)) @@ -86,5 +53,65 @@ internal JsonClaimSet CreateHeaderClaimSet(ReadOnlySpan byteSpan) return new JsonClaimSet(claims); } + + + /// + /// Reads the value of a claim in the header and adds it to the dictionary. + /// Can be overridden to read and add custom claims. + /// If a custom claim is read, the reader should be positioned at the next token after reading the claim. + /// + /// The Utf8JsonReader instance positioned at a claim name token used to read the JSON payload. + /// Collection of claims that have been read from the reader. + private protected virtual void ReadHeaderValue(ref Utf8JsonReader reader, IDictionary claims) + { + if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Alg)) + { + claims[JwtHeaderParameterNames.Alg] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Alg, ClassName, true); + } + else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Cty)) + { + claims[JwtHeaderParameterNames.Cty] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Cty, ClassName, true); + } + else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Kid)) + { + claims[JwtHeaderParameterNames.Kid] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Kid, ClassName, true); + } + else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Typ)) + { + claims[JwtHeaderParameterNames.Typ] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Typ, ClassName, true); ; + } + else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.X5t)) + { + claims[JwtHeaderParameterNames.X5t] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.X5t, ClassName, true); + } + else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Zip)) + { + claims[JwtHeaderParameterNames.Zip] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Zip, ClassName, true); + } + else + { + string claimName = reader.GetString(); + + if (TryReadJwtClaim != null) + { + reader.Read(); // Move to the value + if (TryReadJwtClaim(ref reader, JwtSegmentType.Header, claimName, out object claimValue)) + { + claims[claimName] = claimValue; + reader.Read(); // Move to the next token + } + else + { + // The reader is positioned at the value token. The custom delegate did not read the value. Use our own logic. + claims[claimName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, claimName, JsonClaimSet.ClassName, false); + } + } + else + { + // Move the reader forward to the value and read it using our own logic. + claims[claimName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, claimName, JsonClaimSet.ClassName, true); + } + } + } } } diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs index 844594ccc5..2ab0ca3bf4 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs @@ -52,11 +52,18 @@ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan byteSpan) return new JsonClaimSet(claims); } + /// + /// Reads the value of a claim in the payload and adds it to the dictionary. + /// Can be overridden to read and add custom claims. + /// If a custom claim is read, the reader should be positioned at the next token after reading the claim. + /// + /// The Utf8JsonReader instance positioned at a claim name token used to read the JSON payload. + /// Collection of claims that have been read from the reader. private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDictionary claims) { if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Aud)) { - _audiences = []; + List _audiences = []; reader.Read(); if (reader.TokenType == JsonTokenType.StartArray) { @@ -78,46 +85,55 @@ private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDict } else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Azp)) { - _azp = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Azp, ClassName, true); - claims[JwtRegisteredClaimNames.Azp] = _azp; + claims[JwtRegisteredClaimNames.Azp] = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Azp, ClassName, true); } else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Exp)) { - _exp = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Exp, ClassName, true); - _expDateTime = EpochTime.DateTime(_exp.Value); - claims[JwtRegisteredClaimNames.Exp] = _exp; + claims[JwtRegisteredClaimNames.Exp] = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Exp, ClassName, true); } else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iat)) { - _iat = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Iat, ClassName, true); - _iatDateTime = EpochTime.DateTime(_iat.Value); - claims[JwtRegisteredClaimNames.Iat] = _iat; + claims[JwtRegisteredClaimNames.Iat] = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Iat, ClassName, true); } else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iss)) { - _iss = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Iss, ClassName, true); - claims[JwtRegisteredClaimNames.Iss] = _iss; + claims[JwtRegisteredClaimNames.Iss] = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Iss, ClassName, true); } else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Jti)) { - _jti = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Jti, ClassName, true); - claims[JwtRegisteredClaimNames.Jti] = _jti; + claims[JwtRegisteredClaimNames.Jti] = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Jti, ClassName, true); } else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Nbf)) { - _nbf = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Nbf, ClassName, true); - _nbfDateTime = EpochTime.DateTime(_nbf.Value); - claims[JwtRegisteredClaimNames.Nbf] = _nbf; + claims[JwtRegisteredClaimNames.Nbf] = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Nbf, ClassName, true); } else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Sub)) { - _sub = JsonSerializerPrimitives.ReadStringOrNumberAsString(ref reader, JwtRegisteredClaimNames.Sub, ClassName, true); - claims[JwtRegisteredClaimNames.Sub] = _sub; + claims[JwtRegisteredClaimNames.Sub] = JsonSerializerPrimitives.ReadStringOrNumberAsString(ref reader, JwtRegisteredClaimNames.Sub, ClassName, true); } else { - string propertyName = reader.GetString(); - claims[propertyName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, propertyName, JsonClaimSet.ClassName, true); + string claimName = reader.GetString(); + + if (TryReadJwtClaim != null) + { + reader.Read(); // Move to the value + if (TryReadJwtClaim(ref reader, JwtSegmentType.Payload, claimName, out object claimValue)) + { + claims[claimName] = claimValue; + reader.Read(); // Move to the next token + } + else + { + // The reader is positioned at the value token. The custom delegate did not read the value. Use our own logic. + claims[claimName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, claimName, JsonClaimSet.ClassName, false); + } + } + else + { + // Move the reader forward to the value and read it using our own logic. + claims[claimName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, claimName, JsonClaimSet.ClassName, true); + } } } } diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs index 25a220641a..887e20b55b 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs @@ -83,6 +83,35 @@ public JsonWebToken(string jwtEncodedString) _encodedToken = jwtEncodedString; } + /// + /// Initializes a new instance of from a string in JWS or JWE Compact serialized format. + /// + /// A JSON Web Token that has been serialized in JWS or JWE Compact serialized format. + /// Custom delegate to be called when reading associated claims. + /// Thrown if is null or empty. + /// Thrown if is not in JWS or JWE Compact Serialization format. + /// + /// See: (JWT). + /// See: (JWS). + /// See: (JWE). + /// + /// The contents of the returned have not been validated, the JSON Web Token is simply decoded. Validation can be accomplished using the validation methods in + /// + /// + public JsonWebToken( + string jwtEncodedString, + TryReadJwtClaim tryReadJwtClaim) + { + if (string.IsNullOrEmpty(jwtEncodedString)) + throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(jwtEncodedString))); + + TryReadJwtClaim = tryReadJwtClaim; + + ReadToken(jwtEncodedString.AsMemory()); + + _encodedToken = jwtEncodedString; + } + /// /// Initializes a new instance of from a ReadOnlyMemory{char} in JWS or JWE Compact serialized format. /// @@ -107,6 +136,35 @@ public JsonWebToken(ReadOnlyMemory encodedTokenMemory) _encodedTokenMemory = encodedTokenMemory; } + /// + /// Initializes a new instance of from a ReadOnlyMemory{char} in JWS or JWE Compact serialized format. + /// + /// A ReadOnlyMemory{char} containing the JSON Web Token serialized in JWS or JWE Compact format. + /// Custom delegate to be called when reading associated claims. + /// Thrown if is empty. + /// Thrown if does not represent a valid JWS or JWE Compact Serialization format. + /// + /// See: (JWT). + /// See: (JWS). + /// See: (JWE). + /// + /// The contents of the returned have not been validated; the JSON Web Token is simply decoded. Validation can be performed using the methods in . + /// + /// + public JsonWebToken( + ReadOnlyMemory encodedTokenMemory, + TryReadJwtClaim tryReadJwtClaim) + { + if (encodedTokenMemory.IsEmpty) + throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(encodedTokenMemory))); + + TryReadJwtClaim = tryReadJwtClaim; + + ReadToken(encodedTokenMemory); + + _encodedTokenMemory = encodedTokenMemory; + } + /// /// Initializes a new instance of the class where the header contains the crypto algorithms applied to the encoded header and payload. /// @@ -138,6 +196,60 @@ public JsonWebToken(string header, string payload) _encodedToken = encodedToken; } + /// + /// Initializes a new instance of the class where the header contains the crypto algorithms applied to the encoded header and payload. + /// + /// A string containing JSON which represents the cryptographic operations applied to the JWT and optionally any additional properties of the JWT. + /// A string containing JSON which represents the claims contained in the JWT. Each claim is a JSON object of the form { Name, Value }. Can be the empty. + /// Custom delegate to be called when reading associated claims. + /// + /// See: (JWT). + /// See: (JWS). + /// See: (JWE). + /// + /// The contents of the returned have not been validated, the JSON Web Token is simply decoded. Validation can be accomplished using the validation methods in + /// + /// + /// Thrown if is null or empty. + /// Thrown if is null. + public JsonWebToken( + string header, + string payload, + TryReadJwtClaim tryReadJwtClaim) + { + if (string.IsNullOrEmpty(header)) + throw LogHelper.LogArgumentNullException(nameof(header)); + + _ = payload ?? throw LogHelper.LogArgumentNullException(nameof(payload)); + + var encodedHeader = Base64UrlEncoder.Encode(header); + var encodedPayload = Base64UrlEncoder.Encode(payload); + var encodedToken = encodedHeader + "." + encodedPayload + "."; + + TryReadJwtClaim = tryReadJwtClaim; + + ReadToken(encodedToken.AsMemory()); + + _encodedToken = encodedToken; + } + + /// + /// Gets or sets the delegate that will be called when reading JSON Web Token header and payload claims. + /// + /// + /// An example implementation: + /// + /// bool TryReadJwtClaim(ref Utf8JsonReader reader, JwtSegmentType jwtSegmentType, string claimName, out object claimValue) + /// { + /// if (jwtSegmentType == JwtSegmentType.Payload && claimName == "CustomClaimName") + /// claimValue = JsonSerializer.Deserialize<CustomClaim>(reader.GetString()); + /// return true; + /// return false; + /// } + /// + /// + private TryReadJwtClaim TryReadJwtClaim { get; set; } + internal string ActualIssuer { get; set; } internal ClaimsIdentity ActorClaimsIdentity { get; set; } diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs index 3cbc9e1f95..173c06fe80 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs @@ -552,7 +552,7 @@ private static TokenValidationResult ReadToken(string token, TokenValidationPara #pragma warning disable CA1031 // Do not catch general exception types try { - jsonWebToken = new JsonWebToken(token); + jsonWebToken = new JsonWebToken(token, validationParameters.TryReadJwtClaim); } catch (Exception ex) { diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI.Unshipped.txt index 4f719a327f..1bc1a8faf8 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ -virtual Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ReadJsonWebToken(System.ReadOnlyMemory token) -> Microsoft.IdentityModel.JsonWebTokens.JsonWebToken \ No newline at end of file +Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(string header, string payload, Microsoft.IdentityModel.Tokens.TryReadJwtClaim tryReadJwtClaim) -> void +Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(string jwtEncodedString, Microsoft.IdentityModel.Tokens.TryReadJwtClaim tryReadJwtClaim) -> void +Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(System.ReadOnlyMemory encodedTokenMemory, Microsoft.IdentityModel.Tokens.TryReadJwtClaim tryReadJwtClaim) -> void +virtual Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ReadJsonWebToken(System.ReadOnlyMemory token) -> Microsoft.IdentityModel.JsonWebTokens.JsonWebToken diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net6.0/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net6.0/InternalAPI.Unshipped.txt index e69de29bb2..72c1b80afe 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net6.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net6.0/InternalAPI.Unshipped.txt @@ -0,0 +1,3 @@ +Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.TryReadJwtClaim.get -> Microsoft.IdentityModel.Tokens.TryReadJwtClaim +Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.TryReadJwtClaim.set -> void +virtual Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadHeaderValue(ref System.Text.Json.Utf8JsonReader reader, System.Collections.Generic.IDictionary claims) -> void \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net8.0/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net8.0/InternalAPI.Unshipped.txt index e69de29bb2..72c1b80afe 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net8.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net8.0/InternalAPI.Unshipped.txt @@ -0,0 +1,3 @@ +Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.TryReadJwtClaim.get -> Microsoft.IdentityModel.Tokens.TryReadJwtClaim +Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.TryReadJwtClaim.set -> void +virtual Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadHeaderValue(ref System.Text.Json.Utf8JsonReader reader, System.Collections.Generic.IDictionary claims) -> void \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net9.0/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net9.0/InternalAPI.Unshipped.txt index e69de29bb2..3a3df59a0b 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net9.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/PublicAPI/net9.0/InternalAPI.Unshipped.txt @@ -0,0 +1,3 @@ +Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.TryReadJwtClaim.get -> Microsoft.IdentityModel.Tokens.TryReadJwtClaim +Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.TryReadJwtClaim.set -> void +virtual Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadHeaderValue(ref System.Text.Json.Utf8JsonReader reader, System.Collections.Generic.IDictionary claims) -> void diff --git a/src/Microsoft.IdentityModel.Tokens/Delegates.cs b/src/Microsoft.IdentityModel.Tokens/Delegates.cs index 876d267345..16a564d04a 100644 --- a/src/Microsoft.IdentityModel.Tokens/Delegates.cs +++ b/src/Microsoft.IdentityModel.Tokens/Delegates.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Text.Json; using System.Threading.Tasks; namespace Microsoft.IdentityModel.Tokens @@ -210,4 +211,28 @@ internal delegate ValidationResult SignatureValidationDelegate( BaseConfiguration? configuration, CallContext callContext); #nullable restore + + /// + /// When JSON Web Token header or payload is being read claim by claim, + /// this delegate is called after all claims known to the library have been processed. + /// When called, the reader is positioned at the claim value. + /// + /// + /// An example implementation: + /// + /// bool TryReadJwtClaim(ref Utf8JsonReader reader, JwtSegmentType jwtSegmentType, string claimName, out object claimValue) + /// { + /// if (jwtSegmentType == JwtSegmentType.Payload && claimName == "CustomClaimName") + /// claimValue = JsonSerializer.Deserialize<CustomClaim>(reader.GetString()); + /// return true; + /// return false; + /// } + /// + /// + /// Reader for the underlying token bytes. + /// Specifies whether the claim is from the JWT header or payload. + /// The claim name for this claim value. + /// The claim value that was read and parsed from the reader. + /// True, if the claim value was read successfully; false otherwise. + public delegate bool TryReadJwtClaim(ref Utf8JsonReader reader, JwtSegmentType jwtSegmentType, string claimName, out object claimValue); } diff --git a/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt index e69de29bb2..f58d4437ca 100644 --- a/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt @@ -0,0 +1,6 @@ +Microsoft.IdentityModel.Tokens.JwtSegmentType.Header = 0 -> Microsoft.IdentityModel.Tokens.JwtSegmentType +Microsoft.IdentityModel.Tokens.JwtSegmentType.Payload = 1 -> Microsoft.IdentityModel.Tokens.JwtSegmentType +Microsoft.IdentityModel.Tokens.TokenValidationParameters.TryReadJwtClaim.get -> Microsoft.IdentityModel.Tokens.TryReadJwtClaim +Microsoft.IdentityModel.Tokens.TokenValidationParameters.TryReadJwtClaim.set -> void +Microsoft.IdentityModel.Tokens.TryReadJwtClaim +Microsoft.IdentityModel.Tokens.JwtSegmentType diff --git a/src/Microsoft.IdentityModel.Tokens/TokenEnums.cs b/src/Microsoft.IdentityModel.Tokens/TokenEnums.cs new file mode 100644 index 0000000000..52f832a2f0 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/TokenEnums.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Specifies the segment of a JWT. + /// + public enum JwtSegmentType + { + /// + /// The header segment of the JWT. + /// + Header, + + /// + /// The payload segment of the JWT. + /// + Payload + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs index 09c454ac07..fa6b589762 100644 --- a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs +++ b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs @@ -73,6 +73,7 @@ protected TokenValidationParameters(TokenValidationParameters other) NameClaimType = other.NameClaimType; NameClaimTypeRetriever = other.NameClaimTypeRetriever; PropertyBag = other.PropertyBag; + TryReadJwtClaim = other.TryReadJwtClaim; RefreshBeforeValidation = other.RefreshBeforeValidation; RequireAudience = other.RequireAudience; // CodeQL [SM03926] intentional: Value is copied regardless of whether it is true or false. @@ -455,6 +456,11 @@ public string NameClaimType /// public IDictionary PropertyBag { get; set; } + /// + /// Gets or sets the delegate that will be called when reading JSON Web Token header and payload claims. + /// + public TryReadJwtClaim TryReadJwtClaim { get; set; } + /// /// Gets or sets a boolean to control if configuration required to be refreshed before token validation. /// diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/CustomJsonWebToken.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/CustomJsonWebToken.cs index f0e03afcf1..33aa2726cc 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/CustomJsonWebToken.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/CustomJsonWebToken.cs @@ -10,16 +10,27 @@ namespace Microsoft.IdentityModel.JsonWebTokens.Tests public class CustomJsonWebToken : JsonWebToken { // Represents claims known to this custom implementation and not to the IdentityModel. - public const string CustomClaimName = "CustomClaim"; + // The claim name is the same in the header and the payload, but the value is different. + public const string CustomClaimName = "CustomClaimName"; - private CustomClaim _customClaim; + private CustomClaim _customHeaderClaim; + private CustomClaim _customPayloadClaim; - public CustomClaim CustomClaim + public CustomClaim CustomHeaderClaim { get { - _customClaim ??= Payload.GetValue(CustomClaimName); - return _customClaim; + _customHeaderClaim ??= Header.GetValue(CustomClaimName); + return _customHeaderClaim; + } + } + + public CustomClaim CustomPayloadClaim + { + get + { + _customPayloadClaim ??= Payload.GetValue(CustomClaimName); + return _customPayloadClaim; } } @@ -29,6 +40,24 @@ public CustomJsonWebToken(ReadOnlyMemory encodedTokenMemory) : base(encode public CustomJsonWebToken(string header, string payload) : base(header, payload) { } + private protected override void ReadHeaderValue(ref Utf8JsonReader reader, IDictionary claims) + { + // Handle custom claims. + if (reader.ValueTextEquals(CustomClaimName)) + { + // Deserialize the custom object claim in an appropriate way. + reader.Read(); // Move to the value. + _customHeaderClaim = JsonSerializer.Deserialize(reader.GetString()); + claims[CustomClaimName] = _customHeaderClaim; + reader.Read(); + } + else + { + // Call base implementation to handle other claims known to IdentityModel. + base.ReadHeaderValue(ref reader, claims); + } + } + private protected override void ReadPayloadValue(ref Utf8JsonReader reader, IDictionary claims) { // Handle custom claims. @@ -36,8 +65,8 @@ private protected override void ReadPayloadValue(ref Utf8JsonReader reader, IDic { // Deserialize the custom object claim in an appropriate way. reader.Read(); // Move to the value. - _customClaim = JsonSerializer.Deserialize(reader.GetString()); - claims[CustomClaimName] = _customClaim; + _customPayloadClaim = JsonSerializer.Deserialize(reader.GetString()); + claims[CustomClaimName] = _customPayloadClaim; reader.Read(); } else @@ -50,8 +79,11 @@ private protected override void ReadPayloadValue(ref Utf8JsonReader reader, IDic public class CustomClaim { - public CustomClaim() + public CustomClaim() { } + + public CustomClaim(string value) { + CustomClaimValue = value; } public string CustomClaimValue { get; set; } diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs index 185cb1f860..b18480578d 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs @@ -12,7 +12,9 @@ using System.Reflection; using System.Security.Claims; using System.Text; +using System.Text.Json; using System.Threading; +using System.Threading.Tasks; using Microsoft.IdentityModel.TestUtils; using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens.Json.Tests; @@ -1743,25 +1745,282 @@ public void StringAndMemoryConstructors_CreateEquivalentTokens(JwtTheoryData the [Fact] public void DerivedJsonWebToken_IsCreatedCorrectly() { - var expectedCustomClaim = new CustomClaim() { CustomClaimValue = "customclaim" }; + // Arrange - create token with custom claims + var expectedCustomHeaderClaimValue = new CustomClaim("custom_header_claim"); + var expectedCustomPayloadClaimValue = new CustomClaim("custom_payload_claim"); var tokenStr = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor { Issuer = Default.Issuer, + // The claim name is the same in the header and the payload, but the value is different. + AdditionalHeaderClaims = new Dictionary + { + { CustomJsonWebToken.CustomClaimName, System.Text.Json.JsonSerializer.Serialize(expectedCustomHeaderClaimValue) }, + }, Claims = new Dictionary { - { CustomJsonWebToken.CustomClaimName, System.Text.Json.JsonSerializer.Serialize(expectedCustomClaim) }, + { CustomJsonWebToken.CustomClaimName, System.Text.Json.JsonSerializer.Serialize(expectedCustomPayloadClaimValue) }, } }); + // Act - Create derived token, get custom claims var derivedToken = new CustomJsonWebToken(tokenStr); + derivedToken.TryGetHeaderValue( + CustomJsonWebToken.CustomClaimName, out CustomClaim customHeaderClaim); derivedToken.TryGetPayloadValue( - CustomJsonWebToken.CustomClaimName, out CustomClaim customClaim); + CustomJsonWebToken.CustomClaimName, out CustomClaim customPayloadClaim); + + // Assert custom header claim, custom payload claim, issuer + Assert.Equal(expectedCustomHeaderClaimValue.CustomClaimValue, derivedToken.CustomHeaderClaim.CustomClaimValue); + Assert.Equal(expectedCustomHeaderClaimValue.CustomClaimValue, customHeaderClaim.CustomClaimValue); + + Assert.Equal(expectedCustomPayloadClaimValue.CustomClaimValue, derivedToken.CustomPayloadClaim.CustomClaimValue); + Assert.Equal(expectedCustomPayloadClaimValue.CustomClaimValue, customPayloadClaim.CustomClaimValue); - Assert.Equal(expectedCustomClaim.CustomClaimValue, derivedToken.CustomClaim.CustomClaimValue); - Assert.Equal(expectedCustomClaim.CustomClaimValue, customClaim.CustomClaimValue); Assert.Equal(Default.Issuer, derivedToken.Issuer); } + [Fact] + public void ReadTokenDelegate_UsingJsonWebToken_CalledCorrectly() + { + // Arrange - create token with custom claims + const string customPayloadClaimName1 = "CustomPayload1"; + var expectedCustomHeaderClaim = new CustomClaim("custom_header_claim"); + var expectedCustomPayloadClaim1 = new CustomClaim("custom_payload1"); + var expectedCustomPayloadClaim2 = new CustomClaim("custom_payload2"); + + var tokenSpan = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor + { + Issuer = Default.Issuer, + // The claim name is the same in the header and the payload, but the value is different. + AdditionalHeaderClaims = new Dictionary + { + { CustomJsonWebToken.CustomClaimName, System.Text.Json.JsonSerializer.Serialize(expectedCustomHeaderClaim) }, + }, + Claims = new Dictionary + { + { customPayloadClaimName1, System.Text.Json.JsonSerializer.Serialize(expectedCustomPayloadClaim1) }, + { CustomJsonWebToken.CustomClaimName, System.Text.Json.JsonSerializer.Serialize(expectedCustomPayloadClaim2) }, + } + }).AsMemory(); + + // Arrange - create delegates to read custom claims + CustomClaim headerClaimFromDelegate = null; + CustomClaim payloadClaimFromDelegate1 = null; + CustomClaim payloadClaimFromDelegate2 = null; + bool TryReadJwtClaim(ref Utf8JsonReader reader, JwtSegmentType jwtSegmentType, string claimName, out object claimValue) + { + // Handle custom claims. + switch (jwtSegmentType) + { + case JwtSegmentType.Header: + switch (claimName) + { + case CustomJsonWebToken.CustomClaimName: + headerClaimFromDelegate = System.Text.Json.JsonSerializer.Deserialize(reader.GetString()); + claimValue = headerClaimFromDelegate; + return true; + default: + break; + } + break; + case JwtSegmentType.Payload: + switch (claimName) + { + case customPayloadClaimName1: + payloadClaimFromDelegate1 = System.Text.Json.JsonSerializer.Deserialize(reader.GetString()); + claimValue = payloadClaimFromDelegate1; + return true; + case CustomJsonWebToken.CustomClaimName: + payloadClaimFromDelegate2 = System.Text.Json.JsonSerializer.Deserialize(reader.GetString()); + claimValue = payloadClaimFromDelegate2; + return true; + default: + break; + } + break; + } + claimValue = null; + return false; + } + + // Act - create JsonWebToken with delegates to read custom claims + var jwt = new JsonWebToken( + tokenSpan, + TryReadJwtClaim); + + // Assert custom header claim, custom payload claim, issuer + Assert.True(jwt.TryGetHeaderValue(CustomJsonWebToken.CustomClaimName, out var actualHeaderClaim)); + Assert.Equal(expectedCustomHeaderClaim.CustomClaimValue, actualHeaderClaim.CustomClaimValue); + Assert.NotNull(headerClaimFromDelegate); + Assert.Equal(expectedCustomHeaderClaim.CustomClaimValue, headerClaimFromDelegate.CustomClaimValue); + + Assert.True(jwt.TryGetPayloadValue(customPayloadClaimName1, out var actualPayloadClaim1)); + Assert.Equal(expectedCustomPayloadClaim1.CustomClaimValue, actualPayloadClaim1.CustomClaimValue); + Assert.NotNull(payloadClaimFromDelegate1); + Assert.Equal(expectedCustomPayloadClaim1.CustomClaimValue, payloadClaimFromDelegate1.CustomClaimValue); + + Assert.True(jwt.TryGetPayloadValue(CustomJsonWebToken.CustomClaimName, out var actualPayloadClaim2)); + Assert.Equal(expectedCustomPayloadClaim2.CustomClaimValue, actualPayloadClaim2.CustomClaimValue); + Assert.NotNull(payloadClaimFromDelegate2); + Assert.Equal(expectedCustomPayloadClaim2.CustomClaimValue, payloadClaimFromDelegate2.CustomClaimValue); + } + + [Fact] + public async Task ReadTokenDelegate_UsingJsonWebTokenHandler_CalledCorrectly() + { + // Arrange - create token with custom claims + const string customPayloadClaimName1 = "CustomPayload1"; + var expectedCustomHeaderClaim = new CustomClaim("custom_header_claim"); + var expectedCustomPayloadClaim1 = new CustomClaim("custom_payload1"); + var expectedCustomPayloadClaim2 = new CustomClaim("custom_payload2"); + + var tokenSpan = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor + { + Issuer = Default.Issuer, + // The claim name is the same in the header and the payload, but the value is different. + AdditionalHeaderClaims = new Dictionary + { + { CustomJsonWebToken.CustomClaimName, System.Text.Json.JsonSerializer.Serialize(expectedCustomHeaderClaim) }, + }, + Claims = new Dictionary + { + { customPayloadClaimName1, System.Text.Json.JsonSerializer.Serialize(expectedCustomPayloadClaim1) }, + { CustomJsonWebToken.CustomClaimName, System.Text.Json.JsonSerializer.Serialize(expectedCustomPayloadClaim2) }, + }, + SigningCredentials = KeyingMaterial.JsonWebKeyRsa256SigningCredentials, + }).AsMemory(); + + // Arrange - create delegates to read custom claims + CustomClaim headerClaimFromDelegate = null; + CustomClaim payloadClaimFromDelegate1 = null; + CustomClaim payloadClaimFromDelegate2 = null; + bool TryReadJwtClaim(ref Utf8JsonReader reader, JwtSegmentType jwtSegmentType, string claimName, out object claimValue) + { + // Handle custom claims. + switch (jwtSegmentType) + { + case JwtSegmentType.Header: + switch (claimName) + { + case CustomJsonWebToken.CustomClaimName: + headerClaimFromDelegate = System.Text.Json.JsonSerializer.Deserialize(reader.GetString()); + claimValue = headerClaimFromDelegate; + return true; + default: + break; + } + break; + case JwtSegmentType.Payload: + switch (claimName) + { + case customPayloadClaimName1: + payloadClaimFromDelegate1 = System.Text.Json.JsonSerializer.Deserialize(reader.GetString()); + claimValue = payloadClaimFromDelegate1; + return true; + case CustomJsonWebToken.CustomClaimName: + payloadClaimFromDelegate2 = System.Text.Json.JsonSerializer.Deserialize(reader.GetString()); + claimValue = payloadClaimFromDelegate2; + return true; + default: + break; + } + break; + } + claimValue = null; + return false; + } + + var tokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, + ValidateIssuer = false, + ValidateLifetime = false, + ValidateIssuerSigningKey = false, + IssuerSigningKey = KeyingMaterial.JsonWebKeyRsa256SigningCredentials.Key, + TryReadJwtClaim = TryReadJwtClaim, + }; + + // Act - create JsonWebTokenHandler with delegates to read custom claims + var validationResult = await new JsonWebTokenHandler().ValidateTokenAsync(tokenSpan.ToString(), tokenValidationParameters); + + // Assert custom header claim, custom payload claims + Assert.True(((JsonWebToken)validationResult.SecurityToken).TryGetHeaderValue(CustomJsonWebToken.CustomClaimName, out var actualHeaderClaim)); + Assert.Equal(expectedCustomHeaderClaim.CustomClaimValue, actualHeaderClaim.CustomClaimValue); + Assert.NotNull(headerClaimFromDelegate); + Assert.Equal(expectedCustomHeaderClaim.CustomClaimValue, headerClaimFromDelegate.CustomClaimValue); + + Assert.True(((JsonWebToken)validationResult.SecurityToken).TryGetPayloadValue(customPayloadClaimName1, out var actualPayloadClaim1)); + Assert.Equal(expectedCustomPayloadClaim1.CustomClaimValue, actualPayloadClaim1.CustomClaimValue); + Assert.NotNull(payloadClaimFromDelegate1); + Assert.Equal(expectedCustomPayloadClaim1.CustomClaimValue, payloadClaimFromDelegate1.CustomClaimValue); + + Assert.True(((JsonWebToken)validationResult.SecurityToken).TryGetPayloadValue(CustomJsonWebToken.CustomClaimName, out var actualPayloadClaim2)); + Assert.Equal(expectedCustomPayloadClaim2.CustomClaimValue, actualPayloadClaim2.CustomClaimValue); + Assert.NotNull(payloadClaimFromDelegate2); + Assert.Equal(expectedCustomPayloadClaim2.CustomClaimValue, payloadClaimFromDelegate2.CustomClaimValue); + } + + [Fact] + public void ReadTokenDelegate_CreatesClaimsCorrectly() + { + // Arrange - create token with custom claims + const string customNullPayloadClaimName = "CustomNullPayload"; + const string customStringPayloadClaimName = "CustomStringPayload"; + const string customObjectPayloadClaimName = "CustomObjectPayload"; + var expectedCustomStringPayloadClaim = "custom_string_payload"; + var expectedCustomObjectPayloadClaim = new CustomClaim("custom_string_payload"); + + var tokenSpan = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor + { + Issuer = Default.Issuer, + Claims = new Dictionary + { + { customNullPayloadClaimName, String.Empty }, + { customStringPayloadClaimName, String.Empty }, + { customObjectPayloadClaimName, String.Empty }, + } + }).AsMemory(); + + // Arrange - create delegates to read custom claims + bool TryReadJwtClaim(ref Utf8JsonReader reader, JwtSegmentType jwtSegmentType, string claimName, out object claimValue) + { + // Handle custom claims. + switch (claimName) + { + case customNullPayloadClaimName: + claimValue = null; + return true; + case customStringPayloadClaimName: + claimValue = expectedCustomStringPayloadClaim; + return true; + case customObjectPayloadClaimName: + claimValue = expectedCustomObjectPayloadClaim; + return true; + default: + claimValue = null; + return false; + } + } + + // Act - create JsonWebToken with delegates to read custom claims + var jwt = new JsonWebToken( + tokenSpan, + TryReadJwtClaim); + + // Assert custom claims, issuer + Assert.True(jwt.TryGetClaim(customNullPayloadClaimName, out var actualNullPayloadClaim)); + Assert.Equal(JsonClaimValueTypes.JsonNull, actualNullPayloadClaim.ValueType); + Assert.Equal(string.Empty, actualNullPayloadClaim.Value); + + Assert.True(jwt.TryGetClaim(customStringPayloadClaimName, out var actualStringPayloadClaim)); + Assert.Equal(ClaimValueTypes.String, actualStringPayloadClaim.ValueType); + Assert.Equal(expectedCustomStringPayloadClaim, actualStringPayloadClaim.Value); + + Assert.True(jwt.TryGetClaim(customObjectPayloadClaimName, out var actualObjectPayloadClaim)); + Assert.Equal(ClaimValueTypes.String, actualStringPayloadClaim.ValueType); + Assert.Equal(expectedCustomObjectPayloadClaim.ToString(), actualObjectPayloadClaim.Value); + } + [Fact] public void CreateTokenWithoutKeyIdentifiersInHeader() { diff --git a/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs index edb382d863..2ca873b58b 100644 --- a/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs +++ b/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; @@ -239,6 +240,12 @@ public static SecurityToken TransformBeforeSignatureValidation(SecurityToken tok return token; } + public static bool TryReadJwtClaim(ref Utf8JsonReader reader, JwtSegmentType jwtSegmentType, string claimName, out object claimValue) + { + claimValue = null; + return false; + } + public static string TypeValidator(string type, SecurityToken securityToken, TokenValidationParameters validationParameters) { return type; diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs index a9b9d2dee7..c728e0afa4 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.IdentityModel.Tokens.Tests { public class TokenValidationParametersTests { - int ExpectedPropertyCount = 61; + int ExpectedPropertyCount = 62; // GetSets() compares the total property count which includes internal properties, against a list of public properties, minus delegates. // This allows us to keep track of any properties we are including in the total that are not public nor delegates. @@ -83,6 +83,7 @@ public void Publics() PropertyBag = propertyBag, SignatureValidator = ValidationDelegates.SignatureValidatorReturnsJwtTokenAsIs, SaveSigninToken = true, + TryReadJwtClaim = ValidationDelegates.TryReadJwtClaim, TypeValidator = typeValidator, ValidAlgorithms = validAlgorithms, ValidateAudience = false, @@ -122,6 +123,7 @@ public void Publics() validationParametersSets.PropertyBag = propertyBag; validationParametersSets.SignatureValidator = ValidationDelegates.SignatureValidatorReturnsJwtTokenAsIs; validationParametersSets.SaveSigninToken = true; + validationParametersSets.TryReadJwtClaim = ValidationDelegates.TryReadJwtClaim; validationParametersSets.TypeValidator = typeValidator; validationParametersSets.ValidateAudience = false; validationParametersSets.ValidateIssuer = false; @@ -308,6 +310,7 @@ private TokenValidationParameters CreateTokenValidationParameters() validationParameters.TokenReader = ValidationDelegates.TokenReaderReturnsJsonWebToken; validationParameters.TokenReplayValidator = ValidationDelegates.TokenReplayValidatorReturnsTrue; validationParameters.TransformBeforeSignatureValidation = ValidationDelegates.TransformBeforeSignatureValidation; + validationParameters.TryReadJwtClaim = ValidationDelegates.TryReadJwtClaim; validationParameters.TypeValidator = ValidationDelegates.TypeValidator; validationParameters.ActorValidationParameters = new TokenValidationParameters();