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 7, 2024
1 parent 79460ca commit 6a61dbe
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 50 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
20 changes: 18 additions & 2 deletions src/Microsoft.IdentityModel.Tokens/SecurityTokenDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,19 @@ 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 possible default header claims are <c>alg</c>, <c>kid</c>, <c>x5t</c>, <c>enc</c>, and <c>zip</c>. Note that
/// these are not necessarily present in the header of every token.
///
/// The <c>kid</c> and <c>x5t</c> claims can also be excluded by setting <see cref="IncludeKeyIdInHeader"/> to <c>false</c>.
/// </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 All @@ -108,10 +121,13 @@ public class SecurityTokenDescriptor
public ClaimsIdentity Subject { get; set; }

/// <summary>
/// Indicates if <c>kid</c> and <c>x5t</c> should be included in the header of a JSON web token (JWT)
/// Indicates if <c>kid</c> and <c>x5t</c> should be included in the header of a JSON web token (JWT).
///
/// <remarks>
/// Only applies to JWTs
/// Only applies to JWTs.
///
/// Requires that <c>kid</c> and/or <c>x5t</c> are not included in <see cref="ExcludedDefaultHeaderClaims"/>; otherwise,
/// whichever is/are included in <see cref="ExcludedDefaultHeaderClaims"/> will not be included in the header of the token.
/// </remarks>
/// </summary>
[DefaultValue(true)]
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 6a61dbe

Please sign in to comment.