Skip to content

Commit e810ae2

Browse files
committed
Replace JsonWebToken ReadPayloadValue with a delegate
1 parent 442baa3 commit e810ae2

File tree

9 files changed

+236
-93
lines changed

9 files changed

+236
-93
lines changed

src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler._telemetryClient -> Microsoft.IdentityModel.Telemetry.ITelemetryClient
22
override Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.ValidatedToken>>
33
override Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.ValidatedToken>>
4+
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(string header, string payload, Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate) -> void
5+
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(string jwtEncodedString, Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate) -> void
6+
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(System.ReadOnlyMemory<char> encodedTokenMemory, Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate) -> void
7+
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadTokenPayloadValueDelegate.get -> Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate
8+
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadTokenPayloadValueDelegate.set -> void
9+
static Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadTokenPayloadValue(ref System.Text.Json.Utf8JsonReader reader, System.Collections.Generic.IDictionary<string, object> claims) -> void
410
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.CreateToken(string payload, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> string
511
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.EncryptToken(byte[] innerTokenUtf8Bytes, Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, string compressionAlgorithm, System.Collections.Generic.IDictionary<string, object> additionalHeaderClaims, string tokenType, bool includeKeyIdInHeader) -> string
612
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.EncryptToken(byte[] innerTokenUtf8Bytes, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> string

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal class JsonClaimSet
2828

2929
internal JsonClaimSet()
3030
{
31-
_jsonClaims = new Dictionary<string, object>();
31+
_jsonClaims = [];
3232
}
3333

3434
internal JsonClaimSet(Dictionary<string, object> jsonClaims)

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

+16-21
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Collections.Generic;
66
using System.Text.Json;
77
using Microsoft.IdentityModel.Logging;
8-
using Microsoft.IdentityModel.Tokens;
98
using Microsoft.IdentityModel.Tokens.Json;
109

1110
namespace Microsoft.IdentityModel.JsonWebTokens
@@ -40,7 +39,7 @@ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan<byte> byteSpan)
4039
{
4140
if (reader.TokenType == JsonTokenType.PropertyName)
4241
{
43-
ReadPayloadValue(ref reader, claims);
42+
ReadTokenPayloadValueDelegate(ref reader, claims);
4443
}
4544
// We read a JsonTokenType.StartObject above, exiting and positioning reader at next token.
4645
else if (JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.EndObject, false))
@@ -52,11 +51,17 @@ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan<byte> byteSpan)
5251
return new JsonClaimSet(claims);
5352
}
5453

55-
private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDictionary<string, object> claims)
54+
/// <summary>
55+
/// Reads and saves the value of the payload claim from the reader.
56+
/// </summary>
57+
/// <param name="reader">The reader over the JWT.</param>
58+
/// <param name="claims">A collection to hold claims that have been read.</param>
59+
/// <returns>A claim that was read.</returns>
60+
internal static void ReadTokenPayloadValue(ref Utf8JsonReader reader, IDictionary<string, object> claims)
5661
{
5762
if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Aud))
5863
{
59-
_audiences = [];
64+
List<string> _audiences = [];
6065
reader.Read();
6166
if (reader.TokenType == JsonTokenType.StartArray)
6267
{
@@ -78,41 +83,31 @@ private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDict
7883
}
7984
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Azp))
8085
{
81-
_azp = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Azp, ClassName, true);
82-
claims[JwtRegisteredClaimNames.Azp] = _azp;
86+
claims[JwtRegisteredClaimNames.Azp] = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Azp, ClassName, true);
8387
}
8488
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Exp))
8589
{
86-
_exp = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Exp, ClassName, true);
87-
_expDateTime = EpochTime.DateTime(_exp.Value);
88-
claims[JwtRegisteredClaimNames.Exp] = _exp;
90+
claims[JwtRegisteredClaimNames.Exp] = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Exp, ClassName, true);
8991
}
9092
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iat))
9193
{
92-
_iat = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Iat, ClassName, true);
93-
_iatDateTime = EpochTime.DateTime(_iat.Value);
94-
claims[JwtRegisteredClaimNames.Iat] = _iat;
94+
claims[JwtRegisteredClaimNames.Iat] = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Iat, ClassName, true);
9595
}
9696
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iss))
9797
{
98-
_iss = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Iss, ClassName, true);
99-
claims[JwtRegisteredClaimNames.Iss] = _iss;
98+
claims[JwtRegisteredClaimNames.Iss] = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Iss, ClassName, true);
10099
}
101100
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Jti))
102101
{
103-
_jti = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Jti, ClassName, true);
104-
claims[JwtRegisteredClaimNames.Jti] = _jti;
102+
claims[JwtRegisteredClaimNames.Jti] = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Jti, ClassName, true);
105103
}
106104
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Nbf))
107105
{
108-
_nbf = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Nbf, ClassName, true);
109-
_nbfDateTime = EpochTime.DateTime(_nbf.Value);
110-
claims[JwtRegisteredClaimNames.Nbf] = _nbf;
106+
claims[JwtRegisteredClaimNames.Nbf] = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Nbf, ClassName, true);
111107
}
112108
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Sub))
113109
{
114-
_sub = JsonSerializerPrimitives.ReadStringOrNumberAsString(ref reader, JwtRegisteredClaimNames.Sub, ClassName, true);
115-
claims[JwtRegisteredClaimNames.Sub] = _sub;
110+
claims[JwtRegisteredClaimNames.Sub] = JsonSerializerPrimitives.ReadStringOrNumberAsString(ref reader, JwtRegisteredClaimNames.Sub, ClassName, true);
116111
}
117112
else
118113
{

src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs

+106
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,33 @@ public JsonWebToken(string jwtEncodedString)
8383
_encodedToken = jwtEncodedString;
8484
}
8585

86+
/// <summary>
87+
/// Initializes a new instance of <see cref="JsonWebToken"/> from a string in JWS or JWE Compact serialized format.
88+
/// </summary>
89+
/// <param name="jwtEncodedString">A JSON Web Token that has been serialized in JWS or JWE Compact serialized format.</param>
90+
/// <param name="readTokenPayloadValueDelegate">A custom delegate to be called when each payload claim is being read. If null, default implementation is called.</param>
91+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="jwtEncodedString"/> is null or empty.</exception>
92+
/// <exception cref="ArgumentException">Thrown if <paramref name="jwtEncodedString"/> is not in JWS or JWE Compact Serialization format.</exception>
93+
/// <remarks>
94+
/// See: <see href="https://datatracker.ietf.org/doc/html/rfc7519"/> (JWT).
95+
/// See: <see href="https://datatracker.ietf.org/doc/html/rfc7515"/> (JWS).
96+
/// See: <see href="https://datatracker.ietf.org/doc/html/rfc7516"/> (JWE).
97+
/// <para>
98+
/// The contents of the returned <see cref="JsonWebToken"/> have not been validated, the JSON Web Token is simply decoded. Validation can be accomplished using the validation methods in <see cref="JsonWebTokenHandler"/>
99+
/// </para>
100+
/// </remarks>
101+
internal JsonWebToken(string jwtEncodedString, ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate)
102+
{
103+
if (string.IsNullOrEmpty(jwtEncodedString))
104+
throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(jwtEncodedString)));
105+
106+
ReadTokenPayloadValueDelegate = readTokenPayloadValueDelegate ?? ReadTokenPayloadValue;
107+
108+
ReadToken(jwtEncodedString.AsMemory());
109+
110+
_encodedToken = jwtEncodedString;
111+
}
112+
86113
/// <summary>
87114
/// Initializes a new instance of <see cref="JsonWebToken"/> from a ReadOnlyMemory{char} in JWS or JWE Compact serialized format.
88115
/// </summary>
@@ -107,6 +134,33 @@ public JsonWebToken(ReadOnlyMemory<char> encodedTokenMemory)
107134
_encodedTokenMemory = encodedTokenMemory;
108135
}
109136

137+
/// <summary>
138+
/// Initializes a new instance of <see cref="JsonWebToken"/> from a ReadOnlyMemory{char} in JWS or JWE Compact serialized format.
139+
/// </summary>
140+
/// <param name="encodedTokenMemory">A ReadOnlyMemory{char} containing the JSON Web Token serialized in JWS or JWE Compact format.</param>
141+
/// <param name="readTokenPayloadValueDelegate">A custom delegate to be called when each payload claim is being read. If null, default implementation is called.</param>
142+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="encodedTokenMemory"/> is empty.</exception>
143+
/// <exception cref="ArgumentException">Thrown if <paramref name="encodedTokenMemory"/> does not represent a valid JWS or JWE Compact Serialization format.</exception>
144+
/// <remarks>
145+
/// See: <see href="https://datatracker.ietf.org/doc/html/rfc7519"/> (JWT).
146+
/// See: <see href="https://datatracker.ietf.org/doc/html/rfc7515"/> (JWS).
147+
/// See: <see href="https://datatracker.ietf.org/doc/html/rfc7516"/> (JWE).
148+
/// <para>
149+
/// The contents of the returned <see cref="JsonWebToken"/> have not been validated; the JSON Web Token is simply decoded. Validation can be performed using the methods in <see cref="JsonWebTokenHandler"/>.
150+
/// </para>
151+
/// </remarks>
152+
internal JsonWebToken(ReadOnlyMemory<char> encodedTokenMemory, ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate)
153+
{
154+
if (encodedTokenMemory.IsEmpty)
155+
throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(encodedTokenMemory)));
156+
157+
ReadTokenPayloadValueDelegate = readTokenPayloadValueDelegate ?? ReadTokenPayloadValue;
158+
159+
ReadToken(encodedTokenMemory);
160+
161+
_encodedTokenMemory = encodedTokenMemory;
162+
}
163+
110164
/// <summary>
111165
/// Initializes a new instance of the <see cref="JsonWebToken"/> class where the header contains the crypto algorithms applied to the encoded header and payload.
112166
/// </summary>
@@ -138,6 +192,58 @@ public JsonWebToken(string header, string payload)
138192
_encodedToken = encodedToken;
139193
}
140194

195+
/// <summary>
196+
/// Initializes a new instance of the <see cref="JsonWebToken"/> class where the header contains the crypto algorithms applied to the encoded header and payload.
197+
/// </summary>
198+
/// <param name="header">A string containing JSON which represents the cryptographic operations applied to the JWT and optionally any additional properties of the JWT.</param>
199+
/// <param name="payload">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.</param>
200+
/// <param name="readTokenPayloadValueDelegate">A custom delegate to be called when each payload claim is being read. If null, default implementation is called.</param>
201+
/// <remarks>
202+
/// See: <see href="https://datatracker.ietf.org/doc/html/rfc7519"/> (JWT).
203+
/// See: <see href="https://datatracker.ietf.org/doc/html/rfc7515"/> (JWS).
204+
/// See: <see href="https://datatracker.ietf.org/doc/html/rfc7516"/> (JWE).
205+
/// <para>
206+
/// The contents of the returned <see cref="JsonWebToken"/> have not been validated, the JSON Web Token is simply decoded. Validation can be accomplished using the validation methods in <see cref="JsonWebTokenHandler"/>
207+
/// </para>
208+
/// </remarks>
209+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="header"/> is null or empty.</exception>
210+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="payload"/> is null.</exception>
211+
internal JsonWebToken(string header, string payload, ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate)
212+
{
213+
if (string.IsNullOrEmpty(header))
214+
throw LogHelper.LogArgumentNullException(nameof(header));
215+
216+
_ = payload ?? throw LogHelper.LogArgumentNullException(nameof(payload));
217+
218+
var encodedHeader = Base64UrlEncoder.Encode(header);
219+
var encodedPayload = Base64UrlEncoder.Encode(payload);
220+
var encodedToken = encodedHeader + "." + encodedPayload + ".";
221+
222+
ReadTokenPayloadValueDelegate = readTokenPayloadValueDelegate ?? ReadTokenPayloadValue;
223+
224+
ReadToken(encodedToken.AsMemory());
225+
226+
_encodedToken = encodedToken;
227+
}
228+
229+
/// <summary>
230+
/// Called for each claim when token payload is being read.
231+
/// </summary>
232+
/// <remarks>
233+
/// An example implementation:
234+
/// <code>
235+
/// object ReadPayloadValueDelegate(ref Utf8JsonReader reader, string claimName) =>
236+
/// {
237+
/// if (reader.ValueTextEquals("CustomProp"))
238+
/// {
239+
/// return JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.CustomProp, ClassName, true);
240+
/// }
241+
/// return JsonWebToken.ReadTokenPayloadValue(ref reader, claimName);
242+
/// }
243+
/// </code>
244+
/// </remarks>
245+
internal ReadTokenPayloadValueDelegate ReadTokenPayloadValueDelegate { get; set; } = ReadTokenPayloadValue;
246+
141247
internal string ActualIssuer { get; set; }
142248

143249
internal ClaimsIdentity ActorClaimsIdentity { get; set; }

src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ private static TokenValidationResult ReadToken(string token, TokenValidationPara
514514
#pragma warning disable CA1031 // Do not catch general exception types
515515
try
516516
{
517-
jsonWebToken = new JsonWebToken(token);
517+
jsonWebToken = new JsonWebToken(token, validationParameters.ReadTokenPayloadValue);
518518
}
519519
catch (Exception ex)
520520
{

src/Microsoft.IdentityModel.Tokens/Delegates.cs

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Text.Json;
67
using System.Threading.Tasks;
78

89
namespace Microsoft.IdentityModel.Tokens
@@ -210,4 +211,13 @@ internal delegate ValidationResult<SecurityKey> SignatureValidationDelegate(
210211
BaseConfiguration? configuration,
211212
CallContext callContext);
212213
#nullable restore
214+
215+
/// <summary>
216+
/// Definition for ReadTokenPayloadValueDelegate.
217+
/// Called for each claim when token payload is being read.
218+
/// </summary>
219+
/// <param name="reader">Reader for the underlying token bytes.</param>
220+
/// <param name="claims">A collection to hold claims that have been read.</param>
221+
/// <returns></returns>
222+
internal delegate void ReadTokenPayloadValueDelegate(ref Utf8JsonReader reader, IDictionary<string, object> claims);
213223
}

src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs

+5
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,11 @@ public string NameClaimType
449449
/// </summary>
450450
public IDictionary<string, object> PropertyBag { get; set; }
451451

452+
/// <summary>
453+
/// Gets or sets a delegate that will be called when reading token payload claims.
454+
/// </summary>
455+
internal ReadTokenPayloadValueDelegate ReadTokenPayloadValue { get; set; }
456+
452457
/// <summary>
453458
/// Gets or sets a boolean to control if configuration required to be refreshed before token validation.
454459
/// </summary>

test/Microsoft.IdentityModel.JsonWebTokens.Tests/CustomJsonWebToken.cs

-59
This file was deleted.

0 commit comments

Comments
 (0)