diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs index 1c8bcc20aa..0f40a709ba 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs @@ -13,17 +13,17 @@ public partial class JsonWebToken { internal JsonClaimSet CreateHeaderClaimSet(byte[] bytes) { - return CreateHeaderClaimSet(bytes.AsSpan()); + return CreateHeaderClaimSet(bytes.AsMemory()); } internal JsonClaimSet CreateHeaderClaimSet(byte[] bytes, int length) { - return CreateHeaderClaimSet(bytes.AsSpan(0, length)); + return CreateHeaderClaimSet(bytes.AsMemory(0, length)); } - internal JsonClaimSet CreateHeaderClaimSet(ReadOnlySpan byteSpan) - { - Utf8JsonReader reader = new(byteSpan); + internal JsonClaimSet CreateHeaderClaimSet(Memory tokenHeaderAsMemory) + { + Utf8JsonReader reader = new(tokenHeaderAsMemory.Span); if (!JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.StartObject, true)) throw LogHelper.LogExceptionMessage( new JsonException( @@ -36,46 +36,19 @@ internal JsonClaimSet CreateHeaderClaimSet(ReadOnlySpan byteSpan) LogHelper.MarkAsNonPII(reader.CurrentDepth), LogHelper.MarkAsNonPII(reader.BytesConsumed)))); - Dictionary claims = new(); + Dictionary claims = []; +#if NET8_0_OR_GREATER + Dictionary claimsBytes = []; +#endif while (true) { 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); - } +#if NET8_0_OR_GREATER + ReadHeaderValue(ref reader, claims, claimsBytes, tokenHeaderAsMemory); +#else + ReadHeaderValue(ref reader, claims); +#endif } // We read a JsonTokenType.StartObject above, exiting and positioning reader at next token. else if (JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.EndObject, false)) @@ -84,7 +57,94 @@ internal JsonClaimSet CreateHeaderClaimSet(ReadOnlySpan byteSpan) break; }; +#if NET8_0_OR_GREATER + return new JsonClaimSet(claims, claimsBytes, tokenHeaderAsMemory); +#else return new JsonClaimSet(claims); +#endif + } + + private protected virtual void ReadHeaderValue(ref Utf8JsonReader reader, IDictionary claims) + { + _ = claims ?? throw LogHelper.LogArgumentNullException(nameof(claims)); + + 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); + } + } + +#if NET8_0_OR_GREATER + private protected virtual void ReadHeaderValue( + ref Utf8JsonReader reader, + Dictionary claims, + Dictionary claimsBytes, + Memory tokenAsMemory) + { + _ = claims ?? throw LogHelper.LogArgumentNullException(nameof(claims)); + _ = claimsBytes ?? throw LogHelper.LogArgumentNullException(nameof(claimsBytes)); + + if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Alg)) + { + claimsBytes[JwtHeaderParameterNames.Alg] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtRegisteredClaimNames.Alg, ClassName, true); + } + else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Cty)) + { + claimsBytes[JwtHeaderParameterNames.Cty] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtHeaderParameterNames.Cty, ClassName, true); + } + else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Kid)) + { + claimsBytes[JwtHeaderParameterNames.Kid] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtHeaderParameterNames.Kid, ClassName, true); + } + else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Typ)) + { + claimsBytes[JwtHeaderParameterNames.Typ] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtHeaderParameterNames.Typ, ClassName, true); + } + else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.X5t)) + { + claimsBytes[JwtHeaderParameterNames.X5t] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtHeaderParameterNames.X5t, ClassName, true); + } + else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Zip)) + { + claimsBytes[JwtHeaderParameterNames.Zip] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtHeaderParameterNames.Zip, ClassName, true); + } + else + { + string propertyName = reader.GetString(); + claims[propertyName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, propertyName, JsonClaimSet.ClassName, true); + } } +#endif } } diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs index 20f9fe8dd4..87d594b7c2 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs @@ -62,6 +62,8 @@ internal JsonClaimSet CreatePayloadClaimSet(Memory tokenPayloadAsMemory) private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDictionary claims) { + _ = claims ?? throw LogHelper.LogArgumentNullException(nameof(claims)); + if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Aud)) { _audiences = []; @@ -136,6 +138,9 @@ private protected virtual void ReadPayloadValue( Dictionary claimsBytes, Memory tokenAsMemory) { + _ = claims ?? throw LogHelper.LogArgumentNullException(nameof(claims)); + _ = claimsBytes ?? throw LogHelper.LogArgumentNullException(nameof(claimsBytes)); + if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Aud)) { _audiences = []; diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs index 0c9c5291fc..daecc01458 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs @@ -503,7 +503,7 @@ internal void ReadToken(ReadOnlyMemory encodedTokenMemory) try { - Header = CreateHeaderClaimSet(Base64UrlEncoder.Decode(headerSpan).AsSpan()); + Header = CreateHeaderClaimSet(Base64UrlEncoder.Decode(headerSpan).AsMemory()); } catch (Exception ex) { @@ -572,7 +572,7 @@ internal JsonClaimSet CreateClaimSet(ReadOnlySpan strSpan, int startIndex, byte[] output = new byte[outputSize]; Base64UrlEncoder.Decode(strSpan.Slice(startIndex, length), output); - return createHeaderClaimSet ? CreateHeaderClaimSet(output.AsSpan()) : CreatePayloadClaimSet(output.AsMemory()); + return createHeaderClaimSet ? CreateHeaderClaimSet(output.AsMemory()) : CreatePayloadClaimSet(output.AsMemory()); } /// @@ -1129,64 +1129,43 @@ internal DateTime? ValidToNullable } #endregion - #region Payload Properties Bytes #if NET8_0_OR_GREATER - /// - /// Gets the 'azp' claim from the payload. - /// - /// - /// Identifies the authorized party for the id_token. - /// see: https://openid.net/specs/openid-connect-core-1_0.html - /// - /// If the 'azp' claim is not found, an empty string is returned. - /// - /// - public ReadOnlySpan AzpBytes - { - get - { - return Payload.GetStringBytesValue(JwtRegisteredClaimNames.Azp); - } - } + #region Header Properties Bytes - /// - /// Gets the 'value' of the 'jti' claim from the payload. - /// - /// - /// Provides a unique identifier for the JWT. - /// see: https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7 - /// - /// If the 'jti' claim is not found, an empty string is returned. - /// - /// - public ReadOnlySpan IdBytes - { - get - { - return Payload.GetStringBytesValue(JwtRegisteredClaimNames.Jti); - } - } + /// + public ReadOnlySpan AlgBytes => Payload.GetStringBytesValue(JwtHeaderParameterNames.Alg); - /// - /// Gets the 'value' of the 'iss' claim from the payload. - /// - /// - /// Identifies the principal that issued the JWT. - /// see: https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1 - /// - /// If the 'iss' claim is not found, an empty string is returned. - /// - /// - public ReadOnlySpan IssuerBytes - { - get - { - return Payload.GetStringBytesValue(JwtRegisteredClaimNames.Iss); - } - } + /// + public ReadOnlySpan CtyBytes => Payload.GetStringBytesValue(JwtHeaderParameterNames.Cty); + + /// + public ReadOnlySpan KidBytes => Payload.GetStringBytesValue(JwtHeaderParameterNames.Kid); + + /// + public ReadOnlySpan TypBytes => Payload.GetStringBytesValue(JwtHeaderParameterNames.Typ); + + /// + public ReadOnlySpan X5tBytes => Payload.GetStringBytesValue(JwtHeaderParameterNames.X5t); + + /// + public ReadOnlySpan ZipBytes => Payload.GetStringBytesValue(JwtHeaderParameterNames.Zip); -#endif #endregion + + #region Payload Properties Bytes + + /// + public ReadOnlySpan AzpBytes => Payload.GetStringBytesValue(JwtRegisteredClaimNames.Azp); + + /// + public ReadOnlySpan IdBytes => Payload.GetStringBytesValue(JwtRegisteredClaimNames.Jti); + + /// + public ReadOnlySpan IssuerBytes => Payload.GetStringBytesValue(JwtRegisteredClaimNames.Iss); + + #endregion + +#endif } } diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs index a8e1ba983e..d3f6dd5801 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs @@ -1540,7 +1540,7 @@ public void ParseToken_WithByteProperties() { JwtRegisteredClaimNames.Aud, Default.Audience }, { JwtRegisteredClaimNames.Azp, escapedAzp }, { JwtRegisteredClaimNames.Jti, Default.Jti }, - { "uknown_claim", "unknown_claim_value" }, + { "unknown_claim", "unknown_claim_value" }, } };