Skip to content

Commit f0896e1

Browse files
authored
Replace JsonWebToken ReadPayloadValue with a delegate (#2981)
* Replace JsonWebToken ReadPayloadValue with a delegate * Refactor the single delegate approach into a collection of delegates. * Update the header reader code. Add back the virtual method. Update tests. * Minor changes. Update claim creation. Add tests. * API and test fixes. * Set the delegates to null by default to reduce allocations. * Change delegate dictionary to single delegate. * Enable the delegate to read claims with the same header and payload names. * Rename. * Change visibility.
1 parent faa4998 commit f0896e1

File tree

18 files changed

+606
-74
lines changed

18 files changed

+606
-74
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
virtual Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadHeaderValue(ref System.Text.Json.Utf8JsonReader reader, System.Collections.Generic.IDictionary<string, object> claims) -> void
2+

src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal class JsonClaimSet
3535

3636
internal JsonClaimSet()
3737
{
38-
_jsonClaims = new Dictionary<string, object>();
38+
_jsonClaims = [];
3939
}
4040

4141
internal JsonClaimSet(Dictionary<string, object> jsonClaims)
@@ -88,9 +88,9 @@ internal static void CreateClaimFromObject(List<Claim> claims, string claimType,
8888
claims.Add(new Claim(claimType, m.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Double, issuer, issuer));
8989
else if (value is null)
9090
claims.Add(new Claim(claimType, string.Empty, JsonClaimValueTypes.JsonNull, issuer, issuer));
91-
else if (value is IList ilist)
91+
else if (value is IList iList)
9292
{
93-
foreach (var item in ilist)
93+
foreach (var item in iList)
9494
CreateClaimFromObject(claims, claimType, item, issuer);
9595
}
9696
else if (value is JsonElement j)
@@ -109,6 +109,10 @@ internal static void CreateClaimFromObject(List<Claim> claims, string claimType,
109109
if (claim != null)
110110
claims.Add(claim);
111111
}
112+
else
113+
{
114+
claims.Add(new Claim(claimType, value.ToString(), ClaimValueTypes.String, issuer, issuer));
115+
}
112116
}
113117

114118
internal static Claim CreateClaimFromJsonElement(string claimType, string issuer, JsonElement jsonElement)

src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Text.Json;
77
using Microsoft.IdentityModel.Logging;
8+
using Microsoft.IdentityModel.Tokens;
89
using Microsoft.IdentityModel.Tokens.Json;
910

1011
namespace Microsoft.IdentityModel.JsonWebTokens
@@ -41,41 +42,7 @@ internal JsonClaimSet CreateHeaderClaimSet(ReadOnlySpan<byte> byteSpan)
4142
{
4243
if (reader.TokenType == JsonTokenType.PropertyName)
4344
{
44-
if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Alg))
45-
{
46-
_alg = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Alg, ClassName, true);
47-
claims[JwtHeaderParameterNames.Alg] = _alg;
48-
}
49-
else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Cty))
50-
{
51-
_cty = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Cty, ClassName, true);
52-
claims[JwtHeaderParameterNames.Cty] = _cty;
53-
}
54-
else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Kid))
55-
{
56-
_kid = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Kid, ClassName, true);
57-
claims[JwtHeaderParameterNames.Kid] = _kid;
58-
}
59-
else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Typ))
60-
{
61-
_typ = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Typ, ClassName, true);
62-
claims[JwtHeaderParameterNames.Typ] = _typ;
63-
}
64-
else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.X5t))
65-
{
66-
_x5t = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.X5t, ClassName, true);
67-
claims[JwtHeaderParameterNames.X5t] = _x5t;
68-
}
69-
else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Zip))
70-
{
71-
_zip = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Zip, ClassName, true);
72-
claims[JwtHeaderParameterNames.Zip] = _zip;
73-
}
74-
else
75-
{
76-
string propertyName = reader.GetString();
77-
claims[propertyName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, propertyName, JsonClaimSet.ClassName, true);
78-
}
45+
ReadHeaderValue(ref reader, claims);
7946
}
8047
// We read a JsonTokenType.StartObject above, exiting and positioning reader at next token.
8148
else if (JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.EndObject, false))
@@ -86,5 +53,65 @@ internal JsonClaimSet CreateHeaderClaimSet(ReadOnlySpan<byte> byteSpan)
8653

8754
return new JsonClaimSet(claims);
8855
}
56+
57+
58+
/// <summary>
59+
/// Reads the value of a claim in the header and adds it to the <paramref name="claims"/> dictionary.
60+
/// Can be overridden to read and add custom claims.
61+
/// If a custom claim is read, the reader should be positioned at the next token after reading the claim.
62+
/// </summary>
63+
/// <param name="reader">The Utf8JsonReader instance positioned at a claim name token used to read the JSON payload.</param>
64+
/// <param name="claims">Collection of claims that have been read from the reader.</param>
65+
private protected virtual void ReadHeaderValue(ref Utf8JsonReader reader, IDictionary<string, object> claims)
66+
{
67+
if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Alg))
68+
{
69+
claims[JwtHeaderParameterNames.Alg] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Alg, ClassName, true);
70+
}
71+
else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Cty))
72+
{
73+
claims[JwtHeaderParameterNames.Cty] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Cty, ClassName, true);
74+
}
75+
else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Kid))
76+
{
77+
claims[JwtHeaderParameterNames.Kid] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Kid, ClassName, true);
78+
}
79+
else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Typ))
80+
{
81+
claims[JwtHeaderParameterNames.Typ] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Typ, ClassName, true); ;
82+
}
83+
else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.X5t))
84+
{
85+
claims[JwtHeaderParameterNames.X5t] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.X5t, ClassName, true);
86+
}
87+
else if (reader.ValueTextEquals(JwtHeaderUtf8Bytes.Zip))
88+
{
89+
claims[JwtHeaderParameterNames.Zip] = JsonSerializerPrimitives.ReadString(ref reader, JwtHeaderParameterNames.Zip, ClassName, true);
90+
}
91+
else
92+
{
93+
string claimName = reader.GetString();
94+
95+
if (TryReadJwtClaim != null)
96+
{
97+
reader.Read(); // Move to the value
98+
if (TryReadJwtClaim(ref reader, JwtSegmentType.Header, claimName, out object claimValue))
99+
{
100+
claims[claimName] = claimValue;
101+
reader.Read(); // Move to the next token
102+
}
103+
else
104+
{
105+
// The reader is positioned at the value token. The custom delegate did not read the value. Use our own logic.
106+
claims[claimName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, claimName, JsonClaimSet.ClassName, false);
107+
}
108+
}
109+
else
110+
{
111+
// Move the reader forward to the value and read it using our own logic.
112+
claims[claimName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, claimName, JsonClaimSet.ClassName, true);
113+
}
114+
}
115+
}
89116
}
90117
}

src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,18 @@ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan<byte> byteSpan)
5252
return new JsonClaimSet(claims);
5353
}
5454

55+
/// <summary>
56+
/// Reads the value of a claim in the payload and adds it to the <paramref name="claims"/> dictionary.
57+
/// Can be overridden to read and add custom claims.
58+
/// If a custom claim is read, the reader should be positioned at the next token after reading the claim.
59+
/// </summary>
60+
/// <param name="reader">The Utf8JsonReader instance positioned at a claim name token used to read the JSON payload.</param>
61+
/// <param name="claims">Collection of claims that have been read from the reader.</param>
5562
private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDictionary<string, object> claims)
5663
{
5764
if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Aud))
5865
{
59-
_audiences = [];
66+
List<string> _audiences = [];
6067
reader.Read();
6168
if (reader.TokenType == JsonTokenType.StartArray)
6269
{
@@ -78,46 +85,55 @@ private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDict
7885
}
7986
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Azp))
8087
{
81-
_azp = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Azp, ClassName, true);
82-
claims[JwtRegisteredClaimNames.Azp] = _azp;
88+
claims[JwtRegisteredClaimNames.Azp] = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Azp, ClassName, true);
8389
}
8490
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Exp))
8591
{
86-
_exp = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Exp, ClassName, true);
87-
_expDateTime = EpochTime.DateTime(_exp.Value);
88-
claims[JwtRegisteredClaimNames.Exp] = _exp;
92+
claims[JwtRegisteredClaimNames.Exp] = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Exp, ClassName, true);
8993
}
9094
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iat))
9195
{
92-
_iat = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Iat, ClassName, true);
93-
_iatDateTime = EpochTime.DateTime(_iat.Value);
94-
claims[JwtRegisteredClaimNames.Iat] = _iat;
96+
claims[JwtRegisteredClaimNames.Iat] = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Iat, ClassName, true);
9597
}
9698
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iss))
9799
{
98-
_iss = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Iss, ClassName, true);
99-
claims[JwtRegisteredClaimNames.Iss] = _iss;
100+
claims[JwtRegisteredClaimNames.Iss] = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Iss, ClassName, true);
100101
}
101102
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Jti))
102103
{
103-
_jti = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Jti, ClassName, true);
104-
claims[JwtRegisteredClaimNames.Jti] = _jti;
104+
claims[JwtRegisteredClaimNames.Jti] = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Jti, ClassName, true);
105105
}
106106
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Nbf))
107107
{
108-
_nbf = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Nbf, ClassName, true);
109-
_nbfDateTime = EpochTime.DateTime(_nbf.Value);
110-
claims[JwtRegisteredClaimNames.Nbf] = _nbf;
108+
claims[JwtRegisteredClaimNames.Nbf] = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Nbf, ClassName, true);
111109
}
112110
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Sub))
113111
{
114-
_sub = JsonSerializerPrimitives.ReadStringOrNumberAsString(ref reader, JwtRegisteredClaimNames.Sub, ClassName, true);
115-
claims[JwtRegisteredClaimNames.Sub] = _sub;
112+
claims[JwtRegisteredClaimNames.Sub] = JsonSerializerPrimitives.ReadStringOrNumberAsString(ref reader, JwtRegisteredClaimNames.Sub, ClassName, true);
116113
}
117114
else
118115
{
119-
string propertyName = reader.GetString();
120-
claims[propertyName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, propertyName, JsonClaimSet.ClassName, true);
116+
string claimName = reader.GetString();
117+
118+
if (TryReadJwtClaim != null)
119+
{
120+
reader.Read(); // Move to the value
121+
if (TryReadJwtClaim(ref reader, JwtSegmentType.Payload, claimName, out object claimValue))
122+
{
123+
claims[claimName] = claimValue;
124+
reader.Read(); // Move to the next token
125+
}
126+
else
127+
{
128+
// The reader is positioned at the value token. The custom delegate did not read the value. Use our own logic.
129+
claims[claimName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, claimName, JsonClaimSet.ClassName, false);
130+
}
131+
}
132+
else
133+
{
134+
// Move the reader forward to the value and read it using our own logic.
135+
claims[claimName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, claimName, JsonClaimSet.ClassName, true);
136+
}
121137
}
122138
}
123139
}

0 commit comments

Comments
 (0)