From 887b2164b8429038ef3c3eac0bfecfe75069fb08 Mon Sep 17 00:00:00 2001 From: Brett White Date: Mon, 4 Nov 2024 16:41:46 -0800 Subject: [PATCH] Add ability to exclude default header claims when creating token --- .../InternalAPI.Unshipped.txt | 1 + .../JsonWebTokenHandler.CreateToken.cs | 37 +++++--- .../PublicAPI.Unshipped.txt | 2 + .../SecurityTokenDescriptor.cs | 10 +++ .../InternalAPI.Unshipped.txt | 4 +- .../JwtHeader.cs | 85 ++++++++++++------- .../JwtSecurityTokenHandler.cs | 2 +- .../JsonWebTokenTests.cs | 28 ++++++ 8 files changed, 121 insertions(+), 48 deletions(-) diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt index cb23d777b2..e1b05999eb 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt @@ -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 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 excludedDefaultHeaderClaims) -> bool static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.WriteJweHeader(Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, string compressionAlgorithm, string tokenType, System.Collections.Generic.IDictionary 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 diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs index c05ec8bce1..4c1e0c1fe7 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs @@ -878,6 +878,11 @@ internal static void AddSubjectClaims( nbfSet |= nbfReset; } + internal static bool IncludeDefaultHeaderClaim(string claim, ISet excludedDefaultHeaderClaims) => + excludedDefaultHeaderClaims is null || + excludedDefaultHeaderClaims.Count == 0 || + !excludedDefaultHeaderClaims.Contains(claim); + internal static void WriteJwsHeader( ref Utf8JsonWriter writer, SigningCredentials signingCredentials, @@ -923,19 +928,22 @@ internal static void WriteJwsHeader(ref Utf8JsonWriter writer, SecurityTokenDesc writer.WriteStartObject(); SigningCredentials signingCredentials = tokenDescriptor.SigningCredentials; + ISet 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); } } @@ -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); @@ -998,9 +1006,12 @@ internal static byte[] WriteJweHeader(SecurityTokenDescriptor tokenDescriptor) writer = new Utf8JsonWriter(memoryStream, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); writer.WriteStartObject(); + ISet 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 @@ -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(); @@ -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; @@ -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(); diff --git a/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt index d2c20a77d4..c3ab649acc 100644 --- a/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ +Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.ExcludedDefaultHeaderClaims.get -> System.Collections.Generic.ISet +Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.ExcludedDefaultHeaderClaims.set -> void Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.IncludeKeyIdInHeader.get -> bool Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.IncludeKeyIdInHeader.set -> void diff --git a/src/Microsoft.IdentityModel.Tokens/SecurityTokenDescriptor.cs b/src/Microsoft.IdentityModel.Tokens/SecurityTokenDescriptor.cs index 3b327a48b4..99b3a505fa 100644 --- a/src/Microsoft.IdentityModel.Tokens/SecurityTokenDescriptor.cs +++ b/src/Microsoft.IdentityModel.Tokens/SecurityTokenDescriptor.cs @@ -94,6 +94,16 @@ public class SecurityTokenDescriptor /// public IDictionary AdditionalInnerHeaderClaims { get; set; } + /// + /// Gets or sets the which contains the default header claims that should be excluded from the JWT header. + /// + /// + /// The default header claims are 'alg', 'kid', 'x5t', 'enc', and 'zip'. + /// +#pragma warning disable CA2227 // Collection properties should be read only + public ISet ExcludedDefaultHeaderClaims { get; set; } +#pragma warning restore CA2227 // Collection properties should be read only + /// /// Gets or sets the used to create a security token. /// diff --git a/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt b/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt index 47d5d10352..9bb5a6fd5a 100644 --- a/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt +++ b/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt @@ -1,2 +1,2 @@ -System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, System.Collections.Generic.IDictionary outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary additionalHeaderClaims, bool includeKeyIdInHeader) -> void -System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials, System.Collections.Generic.IDictionary outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary additionalInnerHeaderClaims, bool includeKeyIdInHeader) -> void \ No newline at end of file +System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, System.Collections.Generic.IDictionary outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary additionalHeaderClaims, bool includeKeyIdInHeader, System.Collections.Generic.ISet excludedDefaultHeaderClaims) -> void +System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials, System.Collections.Generic.IDictionary outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary additionalInnerHeaderClaims, bool includeKeyIdInHeader, System.Collections.Generic.ISet excludedDefaultHeaderClaims) -> void \ No newline at end of file diff --git a/src/System.IdentityModel.Tokens.Jwt/JwtHeader.cs b/src/System.IdentityModel.Tokens.Jwt/JwtHeader.cs index 9c0749d839..3ff71227fe 100644 --- a/src/System.IdentityModel.Tokens.Jwt/JwtHeader.cs +++ b/src/System.IdentityModel.Tokens.Jwt/JwtHeader.cs @@ -7,6 +7,7 @@ using System.Text; using System.Text.Encodings.Web; using System.Text.Json; +using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; @@ -132,38 +133,48 @@ public JwtHeader(SigningCredentials signingCredentials, IDictionary will be added as the value for the 'typ' claim in the header. If it is null or empty will be used as token type /// Defines the dictionary containing any custom header claims that need to be added to the inner JWT token header. /// Controls if key identifying information should be stored in the header + /// Default header claims to exclude internal JwtHeader( SigningCredentials signingCredentials, IDictionary outboundAlgorithmMap, string tokenType, IDictionary additionalInnerHeaderClaims, - bool includeKeyIdInHeader) + bool includeKeyIdInHeader, + ISet excludedDefaultHeaderClaims) : base(StringComparer.Ordinal) { if (signingCredentials == null) - this[JwtHeaderParameterNames.Alg] = SecurityAlgorithms.None; - + { + if (JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Alg, excludedDefaultHeaderClaims)) + this[JwtHeaderParameterNames.Alg] = SecurityAlgorithms.None; + } else { - if (outboundAlgorithmMap != null && outboundAlgorithmMap.TryGetValue(signingCredentials.Algorithm, out string outboundAlg)) - Alg = outboundAlg; - else - Alg = signingCredentials.Algorithm; + if (JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Alg, excludedDefaultHeaderClaims)) + { + if (outboundAlgorithmMap != null && outboundAlgorithmMap.TryGetValue(signingCredentials.Algorithm, out string outboundAlg)) + Alg = outboundAlg; + else + Alg = signingCredentials.Algorithm; + } if (includeKeyIdInHeader) { - if (!string.IsNullOrEmpty(signingCredentials.Key.KeyId)) + if (!string.IsNullOrEmpty(signingCredentials.Key.KeyId) && JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Kid, excludedDefaultHeaderClaims)) Kid = signingCredentials.Key.KeyId; - if (signingCredentials is X509SigningCredentials x509SigningCredentials) + if (signingCredentials is X509SigningCredentials x509SigningCredentials && JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.X5t, excludedDefaultHeaderClaims)) this[JwtHeaderParameterNames.X5t] = Base64UrlEncoder.Encode(x509SigningCredentials.Certificate.GetCertHash()); } } - if (string.IsNullOrEmpty(tokenType)) - Typ = JwtConstants.HeaderType; - else - Typ = tokenType; + if (JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Typ, excludedDefaultHeaderClaims)) + { + if (string.IsNullOrEmpty(tokenType)) + Typ = JwtConstants.HeaderType; + else + Typ = tokenType; + } AddAdditionalClaims(additionalInnerHeaderClaims, false); SigningCredentials = signingCredentials; @@ -183,7 +194,7 @@ public JwtHeader( IDictionary outboundAlgorithmMap, string tokenType, IDictionary additionalInnerHeaderClaims) - : this(signingCredentials, outboundAlgorithmMap, tokenType, additionalInnerHeaderClaims, true) + : this(signingCredentials, outboundAlgorithmMap, tokenType, additionalInnerHeaderClaims, true, null) { } /// @@ -223,28 +234,35 @@ public JwtHeader(EncryptingCredentials encryptingCredentials, IDictionary provides the token type /// Defines the dictionary containing any custom header claims that need to be added to the outer JWT token header. /// Controls if key identifying information should be stored in the header + /// Default header claims to exclude /// If 'encryptingCredentials' is null. internal JwtHeader( EncryptingCredentials encryptingCredentials, IDictionary outboundAlgorithmMap, string tokenType, IDictionary additionalHeaderClaims, - bool includeKeyIdInHeader) + bool includeKeyIdInHeader, + ISet excludedDefaultHeaderClaims) : base(StringComparer.Ordinal) { if (encryptingCredentials == null) throw LogHelper.LogArgumentNullException(nameof(encryptingCredentials)); - string outboundAlg; - if (outboundAlgorithmMap != null && outboundAlgorithmMap.TryGetValue(encryptingCredentials.Alg, out outboundAlg)) - Alg = outboundAlg; - else - Alg = encryptingCredentials.Alg; + if (JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Alg, excludedDefaultHeaderClaims)) + { + if (outboundAlgorithmMap != null && outboundAlgorithmMap.TryGetValue(encryptingCredentials.Alg, out string outboundAlg)) + Alg = outboundAlg; + else + Alg = encryptingCredentials.Alg; + } - if (outboundAlgorithmMap != null && outboundAlgorithmMap.TryGetValue(encryptingCredentials.Enc, out outboundAlg)) - Enc = outboundAlg; - else - Enc = encryptingCredentials.Enc; + if (JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Enc, excludedDefaultHeaderClaims)) + { + if (outboundAlgorithmMap != null && outboundAlgorithmMap.TryGetValue(encryptingCredentials.Enc, out string outboundAlg)) + Enc = outboundAlg; + else + 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 @@ -253,23 +271,26 @@ internal JwtHeader( // needs to be maintained. if (AppContextSwitches.UseRfcDefinitionOfEpkAndKid) { - if (includeKeyIdInHeader && !string.IsNullOrEmpty(encryptingCredentials.KeyExchangePublicKey.KeyId)) + if (includeKeyIdInHeader && !string.IsNullOrEmpty(encryptingCredentials.KeyExchangePublicKey.KeyId) && JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Kid, excludedDefaultHeaderClaims)) Kid = encryptingCredentials.KeyExchangePublicKey.KeyId; // Parameter MUST be present [...] when [key agreement] algorithms are used: https://www.rfc-editor.org/rfc/rfc7518#section-4.6.1.1 - if (SupportedAlgorithms.EcdsaWrapAlgorithms.Contains(encryptingCredentials.Alg)) + if (SupportedAlgorithms.EcdsaWrapAlgorithms.Contains(encryptingCredentials.Alg) && JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Epk, excludedDefaultHeaderClaims)) Add(JwtHeaderParameterNames.Epk, JsonWebKeyConverter.ConvertFromSecurityKey(encryptingCredentials.Key).RepresentAsAsymmetricPublicJwk()); } else { - if (includeKeyIdInHeader && !string.IsNullOrEmpty(encryptingCredentials.Key.KeyId)) + if (includeKeyIdInHeader && !string.IsNullOrEmpty(encryptingCredentials.Key.KeyId) && JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Kid, excludedDefaultHeaderClaims)) Kid = encryptingCredentials.Key.KeyId; } - if (string.IsNullOrEmpty(tokenType)) - Typ = JwtConstants.HeaderType; - else - Typ = tokenType; + if (JsonWebTokenHandler.IncludeDefaultHeaderClaim(JwtHeaderParameterNames.Typ, excludedDefaultHeaderClaims)) + { + if (string.IsNullOrEmpty(tokenType)) + Typ = JwtConstants.HeaderType; + else + Typ = tokenType; + } AddAdditionalClaims(additionalHeaderClaims, encryptingCredentials.SetDefaultCtyClaim); EncryptingCredentials = encryptingCredentials; @@ -290,7 +311,7 @@ public JwtHeader( IDictionary outboundAlgorithmMap, string tokenType, IDictionary additionalHeaderClaims) - : this(encryptingCredentials, outboundAlgorithmMap, tokenType, additionalHeaderClaims, true) + : this(encryptingCredentials, outboundAlgorithmMap, tokenType, additionalHeaderClaims, true, null) { } /// diff --git a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs index e9c10ef213..e341abb600 100644 --- a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs +++ b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs @@ -608,7 +608,7 @@ private JwtSecurityToken CreateJwtSecurityTokenPrivate(SecurityTokenDescriptor t JwtPayload payload = new JwtPayload(issuer, tokenDescriptor.Audience, tokenDescriptor.Audiences, (subject == null ? null : OutboundClaimTypeTransform(subject.Claims)), (claimCollection == null ? null : OutboundClaimTypeTransform(claimCollection)), notBefore, expires, issuedAt); SigningCredentials signingCredentials = tokenDescriptor.SigningCredentials; string tokenType = tokenDescriptor.TokenType; - JwtHeader header = new JwtHeader(signingCredentials, OutboundAlgorithmMap, tokenType, tokenDescriptor.AdditionalInnerHeaderClaims, tokenDescriptor.IncludeKeyIdInHeader); + JwtHeader header = new JwtHeader(signingCredentials, OutboundAlgorithmMap, tokenType, tokenDescriptor.AdditionalInnerHeaderClaims, tokenDescriptor.IncludeKeyIdInHeader, tokenDescriptor.ExcludedDefaultHeaderClaims); if (LogHelper.IsEnabled(EventLogLevel.Verbose)) LogHelper.LogVerbose(LogMessages.IDX12721, LogHelper.MarkAsNonPII(issuer ?? "null"), LogHelper.MarkAsNonPII(payload.Aud.ToString() ?? "null")); diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs index e0c2133600..4be6965fab 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs @@ -1771,6 +1771,34 @@ public void CreateTokenWithoutKeyIdentifiersInHeader() Assert.False(token.TryGetHeaderValue(JwtHeaderParameterNames.Kid, out string _)); Assert.False(token.TryGetHeaderValue(JwtHeaderParameterNames.X5t, out string _)); } + + [Fact] + public void CreateTokenWithoutExcludedDefaultHeaderClaims() + { + for (int i = 0; i < 16; i++) + { + var excludedDefaultHeaderClaims = new HashSet(); + if ((i & 8) != 0) + excludedDefaultHeaderClaims.Add(JwtHeaderParameterNames.Alg); + if ((i & 4) != 0) + excludedDefaultHeaderClaims.Add(JwtHeaderParameterNames.Kid); + if ((i & 2) != 0) + excludedDefaultHeaderClaims.Add(JwtHeaderParameterNames.X5t); + if ((i & 1) != 0) + excludedDefaultHeaderClaims.Add(JwtHeaderParameterNames.Typ); + + string rawToken = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor + { + SigningCredentials = KeyingMaterial.X509SigningCreds_1024_RsaSha2_Sha2, + ExcludedDefaultHeaderClaims = excludedDefaultHeaderClaims + }); + var token = new JsonWebToken(rawToken); + Assert.NotEqual(excludedDefaultHeaderClaims.Contains(JwtHeaderParameterNames.Alg), token.TryGetHeaderValue(JwtHeaderParameterNames.Alg, out string _)); + Assert.NotEqual(excludedDefaultHeaderClaims.Contains(JwtHeaderParameterNames.Kid), token.TryGetHeaderValue(JwtHeaderParameterNames.Kid, out string _)); + Assert.NotEqual(excludedDefaultHeaderClaims.Contains(JwtHeaderParameterNames.X5t), token.TryGetHeaderValue(JwtHeaderParameterNames.X5t, out string _)); + Assert.NotEqual(excludedDefaultHeaderClaims.Contains(JwtHeaderParameterNames.Typ), token.TryGetHeaderValue(JwtHeaderParameterNames.Typ, out string _)); + } + } } public class ParseTimeValuesTheoryData : TheoryDataBase