Skip to content

Commit

Permalink
Add ability to exclude default header claims when creating token
Browse files Browse the repository at this point in the history
  • Loading branch information
msbw2 committed Dec 4, 2024
1 parent 1293a5d commit 887b216
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.CreateToken(string payload, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> string
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
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.EncryptToken(byte[] innerTokenUtf8Bytes, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> string
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.IncludeDefaultHeaderClaim(string claim, System.Collections.Generic.ISet<string> excludedDefaultHeaderClaims) -> bool
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[]
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.WriteJweHeader(Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> byte[]
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.WriteJwsHeader(ref System.Text.Json.Utf8JsonWriter writer, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,11 @@ internal static void AddSubjectClaims(
nbfSet |= nbfReset;
}

internal static bool IncludeDefaultHeaderClaim(string claim, ISet<string> excludedDefaultHeaderClaims) =>
excludedDefaultHeaderClaims is null ||
excludedDefaultHeaderClaims.Count == 0 ||
!excludedDefaultHeaderClaims.Contains(claim);

internal static void WriteJwsHeader(
ref Utf8JsonWriter writer,
SigningCredentials signingCredentials,
Expand Down Expand Up @@ -923,19 +928,22 @@ internal static void WriteJwsHeader(ref Utf8JsonWriter writer, SecurityTokenDesc
writer.WriteStartObject();

SigningCredentials signingCredentials = tokenDescriptor.SigningCredentials;
ISet<string> excludedDefaultHeaderClaims = tokenDescriptor.ExcludedDefaultHeaderClaims;
if (signingCredentials == null)
{
writer.WriteString(JwtHeaderUtf8Bytes.Alg, SecurityAlgorithms.None);
if (IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Alg, excludedDefaultHeaderClaims))
writer.WriteString(JwtHeaderUtf8Bytes.Alg, SecurityAlgorithms.None);
}
else
{
writer.WriteString(JwtHeaderUtf8Bytes.Alg, signingCredentials.Algorithm);
if (IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Alg, excludedDefaultHeaderClaims))
writer.WriteString(JwtHeaderUtf8Bytes.Alg, signingCredentials.Algorithm);
if (tokenDescriptor.IncludeKeyIdInHeader)
{
if (signingCredentials.Key.KeyId != null)
if (signingCredentials.Key.KeyId != null && IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Kid, excludedDefaultHeaderClaims))
writer.WriteString(JwtHeaderUtf8Bytes.Kid, signingCredentials.Key.KeyId);

if (signingCredentials.Key is X509SecurityKey x509SecurityKey)
if (signingCredentials.Key is X509SecurityKey x509SecurityKey && IncludeDefaultHeaderClaim(JwtHeaderParameterNames.X5t, excludedDefaultHeaderClaims))
writer.WriteString(JwtHeaderUtf8Bytes.X5t, x509SecurityKey.X5t);
}
}
Expand Down Expand Up @@ -964,7 +972,7 @@ internal static void WriteJwsHeader(ref Utf8JsonWriter writer, SecurityTokenDesc
}
}

if (!typeWritten)
if (!typeWritten && IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Typ, excludedDefaultHeaderClaims))
{
string tokenType = tokenDescriptor.TokenType;
writer.WriteString(JwtHeaderUtf8Bytes.Typ, string.IsNullOrEmpty(tokenType) ? JwtConstants.HeaderType : tokenType);
Expand Down Expand Up @@ -998,9 +1006,12 @@ internal static byte[] WriteJweHeader(SecurityTokenDescriptor tokenDescriptor)
writer = new Utf8JsonWriter(memoryStream, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
writer.WriteStartObject();

ISet<string> excludedDefaultHeaderClaims = tokenDescriptor.ExcludedDefaultHeaderClaims;
EncryptingCredentials encryptingCredentials = tokenDescriptor.EncryptingCredentials;
writer.WriteString(JwtHeaderUtf8Bytes.Alg, encryptingCredentials.Alg);
writer.WriteString(JwtHeaderUtf8Bytes.Enc, encryptingCredentials.Enc);
if (IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Alg, excludedDefaultHeaderClaims))
writer.WriteString(JwtHeaderUtf8Bytes.Alg, encryptingCredentials.Alg);
if (IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Enc, excludedDefaultHeaderClaims))
writer.WriteString(JwtHeaderUtf8Bytes.Enc, encryptingCredentials.Enc);

// Since developers may have already worked around this issue, implicitly taking a dependency on the
// old behavior, we guard the new behavior behind an AppContext switch. The new/RFC-conforming behavior
Expand All @@ -1010,10 +1021,10 @@ internal static byte[] WriteJweHeader(SecurityTokenDescriptor tokenDescriptor)
bool includeKeyIdInHeader = tokenDescriptor.IncludeKeyIdInHeader;
if (AppContextSwitches.UseRfcDefinitionOfEpkAndKid)
{
if (includeKeyIdInHeader && encryptingCredentials.KeyExchangePublicKey.KeyId != null)
if (includeKeyIdInHeader && encryptingCredentials.KeyExchangePublicKey.KeyId != null && IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Kid, excludedDefaultHeaderClaims))
writer.WriteString(JwtHeaderUtf8Bytes.Kid, encryptingCredentials.KeyExchangePublicKey.KeyId);

if (SupportedAlgorithms.EcdsaWrapAlgorithms.Contains(encryptingCredentials.Alg))
if (SupportedAlgorithms.EcdsaWrapAlgorithms.Contains(encryptingCredentials.Alg) && IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Epk, excludedDefaultHeaderClaims))
{
writer.WritePropertyName(JwtHeaderUtf8Bytes.Epk);
string publicJwk = JsonWebKeyConverter.ConvertFromSecurityKey(encryptingCredentials.Key).RepresentAsAsymmetricPublicJwk();
Expand All @@ -1026,12 +1037,12 @@ internal static byte[] WriteJweHeader(SecurityTokenDescriptor tokenDescriptor)
}
else
{
if (includeKeyIdInHeader && encryptingCredentials.Key.KeyId != null)
if (includeKeyIdInHeader && encryptingCredentials.Key.KeyId != null && IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Kid, excludedDefaultHeaderClaims))
writer.WriteString(JwtHeaderUtf8Bytes.Kid, encryptingCredentials.Key.KeyId);
}

string compressionAlgorithm = tokenDescriptor.CompressionAlgorithm;
if (!string.IsNullOrEmpty(compressionAlgorithm))
if (!string.IsNullOrEmpty(compressionAlgorithm) && IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Zip, excludedDefaultHeaderClaims))
writer.WriteString(JwtHeaderUtf8Bytes.Zip, compressionAlgorithm);

bool typeWritten = false;
Expand All @@ -1051,13 +1062,13 @@ internal static byte[] WriteJweHeader(SecurityTokenDescriptor tokenDescriptor)
}
}

if (!typeWritten)
if (!typeWritten && IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Typ, excludedDefaultHeaderClaims))
{
string tokenType = tokenDescriptor.TokenType;
writer.WriteString(JwtHeaderUtf8Bytes.Typ, string.IsNullOrEmpty(tokenType) ? JwtConstants.HeaderType : tokenType);
}

if (!ctyWritten)
if (!ctyWritten && IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Cty, excludedDefaultHeaderClaims))
writer.WriteString(JwtHeaderUtf8Bytes.Cty, JwtConstants.HeaderType);

writer.WriteEndObject();
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.ExcludedDefaultHeaderClaims.get -> System.Collections.Generic.ISet<string>
Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.ExcludedDefaultHeaderClaims.set -> void
Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.IncludeKeyIdInHeader.get -> bool
Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.IncludeKeyIdInHeader.set -> void
10 changes: 10 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/SecurityTokenDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ public class SecurityTokenDescriptor
/// </summary>
public IDictionary<string, object> AdditionalInnerHeaderClaims { get; set; }

/// <summary>
/// Gets or sets the <see cref="HashSet{T}"/> which contains the default header claims that should be excluded from the JWT header.
/// </summary>
/// <remarks>
/// The default header claims are 'alg', 'kid', 'x5t', 'enc', and 'zip'.
/// </remarks>
#pragma warning disable CA2227 // Collection properties should be read only
public ISet<string> ExcludedDefaultHeaderClaims { get; set; }
#pragma warning restore CA2227 // Collection properties should be read only

/// <summary>
/// Gets or sets the <see cref="SigningCredentials"/> used to create a security token.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, System.Collections.Generic.IDictionary<string, string> outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary<string, object> additionalHeaderClaims, bool includeKeyIdInHeader) -> void
System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials, System.Collections.Generic.IDictionary<string, string> outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary<string, object> additionalInnerHeaderClaims, bool includeKeyIdInHeader) -> void
System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, System.Collections.Generic.IDictionary<string, string> outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary<string, object> additionalHeaderClaims, bool includeKeyIdInHeader, System.Collections.Generic.ISet<string> excludedDefaultHeaderClaims) -> void
System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials, System.Collections.Generic.IDictionary<string, string> outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary<string, object> additionalInnerHeaderClaims, bool includeKeyIdInHeader, System.Collections.Generic.ISet<string> excludedDefaultHeaderClaims) -> void
Loading

0 comments on commit 887b216

Please sign in to comment.