Skip to content

Commit 3cbc31c

Browse files
committed
Replace overload with delegates to read token values.
1 parent 96b6b36 commit 3cbc31c

File tree

9 files changed

+124
-90
lines changed

9 files changed

+124
-90
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(System.ReadOnlyMemory<char> encodedTokenMemory, Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate) -> void
2+
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadTokenPayloadValueDelegate.get -> Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate
3+
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadTokenPayloadValueDelegate.set -> void
4+
static Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.ReadTokenPayloadValue(ref System.Text.Json.Utf8JsonReader reader, string claimName) -> object
15
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
26
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.WriteJweHeader(Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, string compressionAlgorithm, string tokenType, System.Collections.Generic.IDictionary<string, object> jweHeaderClaims, bool includeKeyIdInHeader) -> byte[]
37
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.WriteJwsHeader(ref System.Text.Json.Utf8JsonWriter writer, Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials, Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, System.Collections.Generic.IDictionary<string, object> jweHeaderClaims, System.Collections.Generic.IDictionary<string, object> jwsHeaderClaims, string tokenType, bool includeKeyIdInHeader) -> void

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

+20-29
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,8 @@ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan<byte> byteSpan)
4039
{
4140
if (reader.TokenType == JsonTokenType.PropertyName)
4241
{
43-
ReadPayloadValue(ref reader, claims);
42+
string claimName = reader.GetString();
43+
claims[claimName] = ReadTokenPayloadValueDelegate(ref reader, claimName);
4444
}
4545
// We read a JsonTokenType.StartObject above, exiting and positioning reader at next token.
4646
else if (JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.EndObject, false))
@@ -52,72 +52,63 @@ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan<byte> byteSpan)
5252
return new JsonClaimSet(claims);
5353
}
5454

55-
private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDictionary<string, object> claims)
55+
/// <summary>
56+
/// Reads and saves the value of the payload claim from the reader.
57+
/// </summary>
58+
/// <param name="reader">The reader over the JWT.</param>
59+
/// <param name="claimName">The claim at the current position of the reader.</param>
60+
/// <returns>A claim that was read.</returns>
61+
internal static object ReadTokenPayloadValue(ref Utf8JsonReader reader, string claimName)
5662
{
5763
if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Aud))
5864
{
59-
_audiences = [];
65+
List<string> _audiences = [];
6066
reader.Read();
6167
if (reader.TokenType == JsonTokenType.StartArray)
6268
{
6369
JsonSerializerPrimitives.ReadStringsSkipNulls(ref reader, _audiences, JwtRegisteredClaimNames.Aud, ClassName);
64-
claims[JwtRegisteredClaimNames.Aud] = _audiences;
6570
}
6671
else
6772
{
6873
if (reader.TokenType != JsonTokenType.Null)
6974
{
7075
_audiences.Add(JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Aud, ClassName));
71-
claims[JwtRegisteredClaimNames.Aud] = _audiences[0];
72-
}
73-
else
74-
{
75-
claims[JwtRegisteredClaimNames.Aud] = _audiences;
76+
return _audiences[0];
7677
}
7778
}
79+
return _audiences;
7880
}
7981
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Azp))
8082
{
81-
_azp = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Azp, ClassName, true);
82-
claims[JwtRegisteredClaimNames.Azp] = _azp;
83+
return JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Azp, ClassName, true);
8384
}
8485
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Exp))
8586
{
86-
_exp = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Exp, ClassName, true);
87-
_expDateTime = EpochTime.DateTime(_exp.Value);
88-
claims[JwtRegisteredClaimNames.Exp] = _exp;
87+
return JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Exp, ClassName, true);
8988
}
9089
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iat))
9190
{
92-
_iat = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Iat, ClassName, true);
93-
_iatDateTime = EpochTime.DateTime(_iat.Value);
94-
claims[JwtRegisteredClaimNames.Iat] = _iat;
91+
return JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Iat, ClassName, true);
9592
}
9693
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iss))
9794
{
98-
_iss = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Iss, ClassName, true);
99-
claims[JwtRegisteredClaimNames.Iss] = _iss;
95+
return JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Iss, ClassName, true);
10096
}
10197
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Jti))
10298
{
103-
_jti = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Jti, ClassName, true);
104-
claims[JwtRegisteredClaimNames.Jti] = _jti;
99+
return JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Jti, ClassName, true);
105100
}
106101
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Nbf))
107102
{
108-
_nbf = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Nbf, ClassName, true);
109-
_nbfDateTime = EpochTime.DateTime(_nbf.Value);
110-
claims[JwtRegisteredClaimNames.Nbf] = _nbf;
103+
return JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Nbf, ClassName, true);
111104
}
112105
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Sub))
113106
{
114-
_sub = JsonSerializerPrimitives.ReadStringOrNumberAsString(ref reader, JwtRegisteredClaimNames.Sub, ClassName, true);
115-
claims[JwtRegisteredClaimNames.Sub] = _sub;
107+
return JsonSerializerPrimitives.ReadStringOrNumberAsString(ref reader, JwtRegisteredClaimNames.Sub, ClassName, true);
116108
}
117109
else
118110
{
119-
string propertyName = reader.GetString();
120-
claims[propertyName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, propertyName, JsonClaimSet.ClassName, true);
111+
return JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, claimName, JsonClaimSet.ClassName, true);
121112
}
122113
}
123114
}

src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs

+37
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,25 @@ public JsonWebToken(string jwtEncodedString)
8686
_encodedToken = jwtEncodedString;
8787
}
8888

89+
/// <summary>
90+
/// Initializes a new instance of <see cref="JsonWebToken"/> from a ReadOnlyMemory{char} in JWS or JWE Compact serialized format.
91+
/// </summary>
92+
/// <param name="encodedTokenMemory">A ReadOnlyMemory{char} containing the JSON Web Token serialized in JWS or JWE Compact format.</param>
93+
/// <param name="readTokenPayloadValueDelegate">A custom delegate to be called when each payload claim is being read. If null, default implementation is called.</param>
94+
internal JsonWebToken(
95+
ReadOnlyMemory<char> encodedTokenMemory,
96+
ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate)
97+
{
98+
if (encodedTokenMemory.IsEmpty)
99+
throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(encodedTokenMemory)));
100+
101+
ReadTokenPayloadValueDelegate = readTokenPayloadValueDelegate ?? ReadTokenPayloadValue;
102+
103+
ReadToken(encodedTokenMemory);
104+
105+
_encodedTokenMemory = encodedTokenMemory;
106+
}
107+
89108
/// <summary>
90109
/// Initializes a new instance of <see cref="JsonWebToken"/> from a ReadOnlyMemory{char} in JWS or JWE Compact serialized format.
91110
/// </summary>
@@ -141,6 +160,24 @@ public JsonWebToken(string header, string payload)
141160
_encodedToken = encodedToken;
142161
}
143162

163+
/// <summary>
164+
/// Called for each claim when token payload is being read.
165+
/// </summary>
166+
/// <remarks>
167+
/// An example implementation:
168+
/// <code>
169+
/// object ReadPayloadValueDelegate(ref Utf8JsonReader reader, string claimName) =>
170+
/// {
171+
/// if (reader.ValueTextEquals("CustomProp"))
172+
/// {
173+
/// return JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.CustomProp, ClassName, true);
174+
/// }
175+
/// return JsonWebToken.ReadTokenPayloadValue(ref reader, claimName);
176+
/// }
177+
/// </code>
178+
/// </remarks>
179+
internal ReadTokenPayloadValueDelegate ReadTokenPayloadValueDelegate { get; set; } = ReadTokenPayloadValue;
180+
144181
internal string ActualIssuer { get; set; }
145182

146183
internal ClaimsIdentity ActorClaimsIdentity { get; set; }

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
@@ -206,4 +207,13 @@ namespace Microsoft.IdentityModel.Tokens
206207
/// <returns>The validated <see cref="SecurityToken"/>.</returns>
207208
internal delegate ValidationResult<SecurityKey> SignatureValidationDelegate(SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext? callContext);
208209
#nullable restore
210+
211+
/// <summary>
212+
/// Definition for ReadTokenPayloadValueDelegate.
213+
/// Called for each claim when token payload is being read.
214+
/// </summary>
215+
/// <param name="reader">Reader for the underlying token bytes.</param>
216+
/// <param name="claimName">The name of the claim being read.</param>
217+
/// <returns></returns>
218+
internal delegate object ReadTokenPayloadValueDelegate(ref Utf8JsonReader reader, string claimName);
209219
}

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

+3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration
1010
Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedValidationParameters = 2 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource
1111
Microsoft.IdentityModel.Tokens.LifetimeValidationError._expires -> System.DateTime
1212
Microsoft.IdentityModel.Tokens.LifetimeValidationError._notBefore -> System.DateTime
13+
Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate
1314
Microsoft.IdentityModel.Tokens.TokenTypeValidationError
1415
Microsoft.IdentityModel.Tokens.TokenTypeValidationError.TokenTypeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidTokenType) -> void
1516
Microsoft.IdentityModel.Tokens.TokenTypeValidationError._invalidTokenType -> string
17+
Microsoft.IdentityModel.Tokens.TokenValidationParameters.ReadTokenPayloadValue.get -> Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate
18+
Microsoft.IdentityModel.Tokens.TokenValidationParameters.ReadTokenPayloadValue.set -> void
1619
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.get -> System.TimeProvider
1720
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.set -> void
1821
Microsoft.IdentityModel.Tokens.ValidationError.GetException(System.Type exceptionType, System.Exception innerException) -> System.Exception

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

-42
This file was deleted.

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

+44-18
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
using System.Reflection;
1313
using System.Security.Claims;
1414
using System.Text;
15+
using System.Text.Json;
1516
using System.Threading;
1617
using Microsoft.IdentityModel.TestUtils;
1718
using Microsoft.IdentityModel.Tokens;
19+
using Microsoft.IdentityModel.Tokens.Json;
1820
using Microsoft.IdentityModel.Tokens.Json.Tests;
1921
using Newtonsoft.Json;
2022
using Newtonsoft.Json.Linq;
@@ -1741,35 +1743,59 @@ public void StringAndMemoryConstructors_CreateEquivalentTokens(JwtTheoryData the
17411743
}
17421744

17431745
[Fact]
1744-
public void DerivedJsonWebToken_IsCreatedCorrectly()
1746+
public void CreateTokenWithoutKeyIdentifiersInHeader()
17451747
{
1746-
var expectedCustomClaim = "customclaim";
1747-
var tokenStr = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor
1748+
string rawToken = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor
1749+
{
1750+
SigningCredentials = KeyingMaterial.JsonWebKeyRsa256SigningCredentials,
1751+
IncludeKeyIdInHeader = false
1752+
});
1753+
var token = new JsonWebToken(rawToken);
1754+
Assert.False(token.TryGetHeaderValue(JwtHeaderParameterNames.Kid, out string _));
1755+
Assert.False(token.TryGetHeaderValue(JwtHeaderParameterNames.X5t, out string _));
1756+
}
1757+
1758+
[Fact]
1759+
public void ReadTokenDelegates_CalledCorrectly()
1760+
{
1761+
var tokenSpan = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor
17481762
{
17491763
Issuer = Default.Issuer,
17501764
Claims = new Dictionary<string, object>
17511765
{
1752-
{ "CustomClaim", expectedCustomClaim },
1766+
{ "CustomPayload", "custom_payload" },
1767+
},
1768+
AdditionalHeaderClaims = new Dictionary<string, object>
1769+
{
1770+
{ "CustomHeader", "custom_header" }
17531771
}
1754-
});
1772+
}).AsMemory();
1773+
1774+
object ReadPayloadValue(ref Utf8JsonReader reader, string claimName)
1775+
{
1776+
if (reader.ValueTextEquals("CustomPayload"u8))
1777+
{
1778+
return new CustomPayloadClaim(JsonSerializerPrimitives.ReadString(ref reader, "CustomPayload", string.Empty, true));
1779+
}
1780+
return JsonWebToken.ReadTokenPayloadValue(ref reader, claimName);
1781+
}
1782+
1783+
var jwt = new JsonWebToken(tokenSpan, ReadPayloadValue);
17551784

1756-
var derivedToken = new CustomJsonWebToken(tokenStr);
1785+
Assert.True(jwt.TryGetHeaderValue<string>("CustomHeader", out var actualHeaderClaim));
1786+
Assert.True(jwt.TryGetPayloadValue<CustomPayloadClaim>("CustomPayload", out var actualPayloadClaim));
17571787

1758-
Assert.Equal(expectedCustomClaim, derivedToken.CustomClaim);
1759-
Assert.Equal(Default.Issuer, derivedToken.Issuer);
1788+
Assert.Equal("custom_header", actualHeaderClaim);
1789+
Assert.Equal("custom_payload", actualPayloadClaim.CustomValue);
17601790
}
17611791

1762-
[Fact]
1763-
public void CreateTokenWithoutKeyIdentifiersInHeader()
1792+
private class CustomHeaderClaim(string customValue)
17641793
{
1765-
string rawToken = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor
1766-
{
1767-
SigningCredentials = KeyingMaterial.JsonWebKeyRsa256SigningCredentials,
1768-
IncludeKeyIdInHeader = false
1769-
});
1770-
var token = new JsonWebToken(rawToken);
1771-
Assert.False(token.TryGetHeaderValue(JwtHeaderParameterNames.Kid, out string _));
1772-
Assert.False(token.TryGetHeaderValue(JwtHeaderParameterNames.X5t, out string _));
1794+
public string CustomValue { get; set; } = customValue;
1795+
}
1796+
private class CustomPayloadClaim(string customValue)
1797+
{
1798+
public string CustomValue { get; set; } = customValue;
17731799
}
17741800
}
17751801

0 commit comments

Comments
 (0)