diff --git a/benchmark/Microsoft.IdentityModel.Benchmarks/CreateTokenTests.cs b/benchmark/Microsoft.IdentityModel.Benchmarks/CreateTokenTests.cs
index 7f753ba26c..85acf48301 100644
--- a/benchmark/Microsoft.IdentityModel.Benchmarks/CreateTokenTests.cs
+++ b/benchmark/Microsoft.IdentityModel.Benchmarks/CreateTokenTests.cs
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System;
using BenchmarkDotNet.Attributes;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
namespace Microsoft.IdentityModel.Benchmarks
{
- [HideColumns("Type", "Job", "WarmupCount", "LaunchCount")]
[MemoryDiagnoser]
public class CreateTokenTests
{
@@ -17,6 +17,7 @@ public class CreateTokenTests
[GlobalSetup]
public void Setup()
{
+ DateTime now = DateTime.UtcNow;
_jsonWebTokenHandler = new JsonWebTokenHandler();
_tokenDescriptor = new SecurityTokenDescriptor
{
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs
new file mode 100644
index 0000000000..4cd4f53a2d
--- /dev/null
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs
@@ -0,0 +1,1383 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Claims;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using Microsoft.IdentityModel.Abstractions;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Tokens;
+using JsonPrimitives = Microsoft.IdentityModel.Tokens.Json.JsonSerializerPrimitives;
+using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
+
+namespace Microsoft.IdentityModel.JsonWebTokens
+{
+ ///
+ /// A designed for creating and validating Json Web Tokens.
+ /// See: https://datatracker.ietf.org/doc/html/rfc7519 and http://www.rfc-editor.org/info/rfc7515.
+ ///
+ /// This partial class is focused on TokenCreation.
+ public partial class JsonWebTokenHandler : TokenHandler
+ {
+ ///
+ /// Creates an unsigned JWS (Json Web Signature).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// if is null.
+ /// A JWS in Compact Serialization Format.
+ public virtual string CreateToken(
+ string payload)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ return CreateToken(
+ payload,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null);
+ }
+
+ ///
+ /// Creates an unsigned JWS (Json Web Signature).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the dictionary containing any custom header claims that need to be added to the JWT token header.
+ /// if is null.
+ /// if is null.
+ /// A JWS in Compact Serialization Format.
+ public virtual string CreateToken(
+ string payload,
+ IDictionary additionalHeaderClaims)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ _ = additionalHeaderClaims ?? throw LogHelper.LogArgumentNullException(nameof(additionalHeaderClaims));
+
+ return CreateToken(payload,
+ null,
+ null,
+ null,
+ additionalHeaderClaims,
+ null,
+ null);
+ }
+
+ ///
+ /// Creates a JWS (Json Web Signature).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the security key and algorithm that will be used to sign the JWS.
+ /// if is null.
+ /// if is null.
+ /// A JWS in Compact Serialization Format.
+ public virtual string CreateToken(
+ string payload,
+ SigningCredentials signingCredentials)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ _ = signingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(signingCredentials));
+
+ return CreateToken(
+ payload,
+ signingCredentials,
+ null,
+ null,
+ null,
+ null,
+ null);
+ }
+
+ ///
+ /// Creates a JWS (Json Web Signature).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the security key and algorithm that will be used to sign the JWS.
+ /// Defines the dictionary containing any custom header claims that need to be added to the JWT token header.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if ,
+ /// , , and/or
+ /// are present inside of .
+ /// A JWS in Compact Serialization Format.
+ public virtual string CreateToken(
+ string payload,
+ SigningCredentials signingCredentials,
+ IDictionary additionalHeaderClaims)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ _ = signingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(signingCredentials));
+ _ = additionalHeaderClaims ?? throw LogHelper.LogArgumentNullException(nameof(additionalHeaderClaims));
+
+ return CreateToken(
+ payload,
+ signingCredentials,
+ null,
+ null,
+ additionalHeaderClaims,
+ null,
+ null);
+ }
+
+ ///
+ /// Creates a JWt that can be a JWS or JWE.
+ ///
+ /// A that contains details of contents of the token.
+ /// A JWT in Compact Serialization Format.
+ public virtual string CreateToken(SecurityTokenDescriptor tokenDescriptor)
+ {
+ _ = tokenDescriptor ?? throw LogHelper.LogArgumentNullException(nameof(tokenDescriptor));
+
+ if (LogHelper.IsEnabled(EventLogLevel.Warning))
+ {
+ if ((tokenDescriptor.Subject == null || !tokenDescriptor.Subject.Claims.Any())
+ && (tokenDescriptor.Claims == null || !tokenDescriptor.Claims.Any()))
+ LogHelper.LogWarning(
+ LogMessages.IDX14114, LogHelper.MarkAsNonPII(nameof(SecurityTokenDescriptor)), LogHelper.MarkAsNonPII(nameof(SecurityTokenDescriptor.Subject)), LogHelper.MarkAsNonPII(nameof(SecurityTokenDescriptor.Claims)));
+ }
+
+ if (tokenDescriptor.AdditionalHeaderClaims?.Count > 0 && tokenDescriptor.AdditionalHeaderClaims.Keys.Intersect(JwtTokenUtilities.DefaultHeaderParameters, StringComparer.OrdinalIgnoreCase).Any())
+ throw LogHelper.LogExceptionMessage(
+ new SecurityTokenException(
+ LogHelper.FormatInvariant(
+ LogMessages.IDX14116,
+ LogHelper.MarkAsNonPII(nameof(tokenDescriptor.AdditionalHeaderClaims)),
+ LogHelper.MarkAsNonPII(string.Join(", ", JwtTokenUtilities.DefaultHeaderParameters)))));
+
+ if (tokenDescriptor.AdditionalInnerHeaderClaims?.Count > 0 && tokenDescriptor.AdditionalInnerHeaderClaims.Keys.Intersect(JwtTokenUtilities.DefaultHeaderParameters, StringComparer.OrdinalIgnoreCase).Any())
+ throw LogHelper.LogExceptionMessage(
+ new SecurityTokenException(
+ LogHelper.FormatInvariant(
+ LogMessages.IDX14116,
+ LogHelper.MarkAsNonPII(nameof(tokenDescriptor.AdditionalInnerHeaderClaims)),
+ LogHelper.MarkAsNonPII(string.Join(", ", JwtTokenUtilities.DefaultHeaderParameters)))));
+
+ return CreateToken(
+ tokenDescriptor,
+ SetDefaultTimesOnTokenCreation,
+ TokenLifetimeInMinutes);
+ }
+
+ internal static string CreateToken(
+ SecurityTokenDescriptor tokenDescriptor,
+ bool setdefaultTimesOnTokenCreation,
+ int tokenLifetimeInMinutes)
+ {
+ // The form of a JWS is: Base64UrlEncoding(UTF8(Header)) | . | Base64UrlEncoding(Payload) | . | Base64UrlEncoding(Signature)
+ // Where the Header is specifically the UTF8 bytes of the JSON, whereas the Payload encoding is not specified, but UTF8 is used by everyone.
+ // The signature is over ASCII(Utf8Bytes(Base64UrlEncoding(Header) | . | Base64UrlEncoding(Payload)))
+ // Since it is not known how large the JWS will be, a MemoryStream is used.
+ // An ArrayBufferWriter was benchmarked, while slightly faster, more memory is used and different code would be needed for 461+ and net6.0+
+ //
+ // net6.0 has added api's that allow passing an allocated buffer when calculating the signature, so ArrayPool.Rent can be used.
+
+ using (MemoryStream utf8ByteMemoryStream = new())
+ {
+ Utf8JsonWriter writer = null;
+ char[] encodedChars = null;
+ byte[] asciiBytes = null;
+ byte[] signatureBytes = null;
+
+ try
+ {
+ writer = new(utf8ByteMemoryStream, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
+
+ WriteJwsHeader(
+ ref writer,
+ tokenDescriptor.SigningCredentials,
+ tokenDescriptor.EncryptingCredentials,
+ tokenDescriptor.AdditionalHeaderClaims,
+ tokenDescriptor.AdditionalInnerHeaderClaims,
+ tokenDescriptor.TokenType);
+
+ // mark length of jwt header
+ int headerLength = (int)utf8ByteMemoryStream.Length;
+
+ // reset the writer and write the payload
+ writer.Reset();
+ WriteJwsPayload(
+ ref writer,
+ tokenDescriptor,
+ setdefaultTimesOnTokenCreation,
+ tokenLifetimeInMinutes);
+
+ // mark end of payload
+ int payloadEnd = (int)utf8ByteMemoryStream.Length;
+ int signatureSize = 0;
+ if (tokenDescriptor.SigningCredentials != null)
+ signatureSize = SupportedAlgorithms.GetMaxByteCount(tokenDescriptor.SigningCredentials.Algorithm);
+
+ int encodedBufferSize = (payloadEnd + 4 + signatureSize) / 3 * 4;
+ encodedChars = ArrayPool.Shared.Rent(encodedBufferSize + 4);
+
+ // Base64UrlEncode the Header
+ int sizeOfEncodedHeader = Base64UrlEncoder.Encode(utf8ByteMemoryStream.GetBuffer().AsSpan(0, headerLength), encodedChars);
+ encodedChars[sizeOfEncodedHeader] = '.';
+ int sizeOfEncodedPayload = Base64UrlEncoder.Encode(utf8ByteMemoryStream.GetBuffer().AsSpan(headerLength, payloadEnd - headerLength), encodedChars.AsSpan(sizeOfEncodedHeader + 1));
+ // encodeChars => 'EncodedHeader.EncodedPayload'
+
+ // Get ASCII Bytes of 'EncodedHeader.EncodedPayload' which is used to calculate the signature
+ asciiBytes = ArrayPool.Shared.Rent(Encoding.ASCII.GetMaxByteCount(encodedBufferSize));
+ int sizeOfEncodedHeaderAndPayloadAsciiBytes
+ = Encoding.ASCII.GetBytes(encodedChars, 0, sizeOfEncodedHeader + sizeOfEncodedPayload + 1, asciiBytes, 0);
+
+ encodedChars[sizeOfEncodedHeader + sizeOfEncodedPayload + 1] = '.';
+ // encodedChars => 'EncodedHeader.EncodedPayload.'
+
+ int sizeOfEncodedSignature = 0;
+ if (tokenDescriptor.SigningCredentials != null)
+ {
+#if NET6_0_OR_GREATER
+ signatureBytes = ArrayPool.Shared.Rent(signatureSize);
+ bool signatureSucceeded = JwtTokenUtilities.CreateSignature(
+ asciiBytes.AsSpan(0, sizeOfEncodedHeaderAndPayloadAsciiBytes),
+ signatureBytes,
+ tokenDescriptor.SigningCredentials,
+ out int signatureLength);
+#else
+ signatureBytes = JwtTokenUtilities.CreateEncodedSignature(asciiBytes, 0, sizeOfEncodedHeaderAndPayloadAsciiBytes, tokenDescriptor.SigningCredentials);
+ int signatureLength = signatureBytes.Length;
+#endif
+ sizeOfEncodedSignature = Base64UrlEncoder.Encode(signatureBytes.AsSpan(0, signatureLength), encodedChars.AsSpan(sizeOfEncodedHeader + sizeOfEncodedPayload + 2));
+ }
+
+ if (tokenDescriptor.EncryptingCredentials != null)
+ {
+ return EncryptToken(
+ Encoding.UTF8.GetBytes(encodedChars, 0, sizeOfEncodedHeader + sizeOfEncodedPayload + sizeOfEncodedSignature + 2),
+ tokenDescriptor.EncryptingCredentials,
+ tokenDescriptor.CompressionAlgorithm,
+ tokenDescriptor.AdditionalHeaderClaims,
+ tokenDescriptor.TokenType);
+ }
+ else
+ {
+ return encodedChars.AsSpan(0, sizeOfEncodedHeader + sizeOfEncodedPayload + sizeOfEncodedSignature + 2).ToString();
+ }
+ }
+ finally
+ {
+ if (encodedChars is not null)
+ ArrayPool.Shared.Return(encodedChars);
+#if NET6_0_OR_GREATER
+ if (signatureBytes is not null)
+ ArrayPool.Shared.Return(signatureBytes);
+#endif
+ if (asciiBytes is not null)
+ ArrayPool.Shared.Return(asciiBytes);
+
+ writer?.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Creates a JWE (Json Web Encryption).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the security key and algorithm that will be used to encrypt the JWT.
+ /// A JWE in compact serialization format.
+ public virtual string CreateToken(
+ string payload,
+ EncryptingCredentials encryptingCredentials)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ _ = encryptingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(encryptingCredentials));
+
+ return CreateToken(
+ payload,
+ null,
+ encryptingCredentials,
+ null,
+ null,
+ null,
+ null);
+ }
+
+ ///
+ /// Creates a JWE (Json Web Encryption).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the security key and algorithm that will be used to encrypt the JWT.
+ /// Defines the dictionary containing any custom header claims that need to be added to the outer JWT token header.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if ,
+ /// , , and/or
+ /// are present inside of .
+ /// A JWS in Compact Serialization Format.
+ public virtual string CreateToken(
+ string payload,
+ EncryptingCredentials encryptingCredentials,
+ IDictionary additionalHeaderClaims)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ _ = encryptingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(encryptingCredentials));
+ _ = additionalHeaderClaims ?? throw LogHelper.LogArgumentNullException(nameof(additionalHeaderClaims));
+
+ return CreateToken(
+ payload,
+ null,
+ encryptingCredentials,
+ null,
+ additionalHeaderClaims,
+ null,
+ null);
+ }
+
+ ///
+ /// Creates a JWE (Json Web Encryption).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the security key and algorithm that will be used to sign the JWT.
+ /// Defines the security key and algorithm that will be used to encrypt the JWT.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// A JWE in compact serialization format.
+ public virtual string CreateToken(
+ string payload,
+ SigningCredentials signingCredentials,
+ EncryptingCredentials encryptingCredentials)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ _ = signingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(signingCredentials));
+ _ = encryptingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(encryptingCredentials));
+
+ return CreateToken(
+ payload,
+ signingCredentials,
+ encryptingCredentials,
+ null,
+ null,
+ null,
+ null);
+ }
+
+ ///
+ /// Creates a JWE (Json Web Encryption).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the security key and algorithm that will be used to sign the JWT.
+ /// Defines the security key and algorithm that will be used to encrypt the JWT.
+ /// Defines the dictionary containing any custom header claims that need to be added to the outer JWT token header.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if ,
+ /// , , and/or
+ /// are present inside of .
+ /// A JWE in compact serialization format.
+ public virtual string CreateToken(
+ string payload,
+ SigningCredentials signingCredentials,
+ EncryptingCredentials encryptingCredentials,
+ IDictionary additionalHeaderClaims)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ _ = signingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(signingCredentials));
+ _ = encryptingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(encryptingCredentials));
+ _ = additionalHeaderClaims ?? throw LogHelper.LogArgumentNullException(nameof(additionalHeaderClaims));
+
+ return CreateToken(
+ payload,
+ signingCredentials,
+ encryptingCredentials,
+ null,
+ additionalHeaderClaims,
+ null,
+ null);
+ }
+
+ ///
+ /// Creates a JWE (Json Web Encryption).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the security key and algorithm that will be used to encrypt the JWT.
+ /// Defines the compression algorithm that will be used to compress the JWT token payload.
+ /// A JWE in compact serialization format.
+ public virtual string CreateToken(
+ string payload,
+ EncryptingCredentials encryptingCredentials,
+ string compressionAlgorithm)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ if (string.IsNullOrEmpty(compressionAlgorithm))
+ throw LogHelper.LogArgumentNullException(nameof(compressionAlgorithm));
+
+ _ = encryptingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(encryptingCredentials));
+
+ return CreateToken(
+ payload,
+ null,
+ encryptingCredentials,
+ compressionAlgorithm,
+ null,
+ null,
+ null);
+ }
+
+ ///
+ /// Creates a JWE (Json Web Encryption).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the security key and algorithm that will be used to sign the JWT.
+ /// Defines the security key and algorithm that will be used to encrypt the JWT.
+ /// Defines the compression algorithm that will be used to compress the JWT token payload.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// A JWE in compact serialization format.
+ public virtual string CreateToken(
+ string payload,
+ SigningCredentials signingCredentials,
+ EncryptingCredentials encryptingCredentials,
+ string compressionAlgorithm)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ if (string.IsNullOrEmpty(compressionAlgorithm))
+ throw LogHelper.LogArgumentNullException(nameof(compressionAlgorithm));
+
+ _ = signingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(signingCredentials));
+ _ = encryptingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(encryptingCredentials));
+
+ return CreateToken(
+ payload,
+ signingCredentials,
+ encryptingCredentials,
+ compressionAlgorithm,
+ null,
+ null,
+ null);
+ }
+
+ ///
+ /// Creates a JWE (Json Web Encryption).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the security key and algorithm that will be used to sign the JWT.
+ /// Defines the security key and algorithm that will be used to encrypt the JWT.
+ /// Defines the compression algorithm that will be used to compress the JWT token payload.
+ /// Defines the dictionary containing any custom header claims that need to be added to the outer JWT token header.
+ /// Defines the dictionary containing any custom header claims that need to be added to the inner JWT token header.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if ,
+ /// , , and/or
+ /// are present inside of .
+ /// A JWE in compact serialization format.
+ public virtual string CreateToken(
+ string payload,
+ SigningCredentials signingCredentials,
+ EncryptingCredentials encryptingCredentials,
+ string compressionAlgorithm,
+ IDictionary additionalHeaderClaims,
+ IDictionary additionalInnerHeaderClaims)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ if (string.IsNullOrEmpty(compressionAlgorithm))
+ throw LogHelper.LogArgumentNullException(nameof(compressionAlgorithm));
+
+ _ = signingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(signingCredentials));
+ _ = encryptingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(encryptingCredentials));
+ _ = additionalHeaderClaims ?? throw LogHelper.LogArgumentNullException(nameof(additionalHeaderClaims));
+ _ = additionalInnerHeaderClaims ?? throw LogHelper.LogArgumentNullException(nameof(additionalInnerHeaderClaims));
+
+ return CreateToken(
+ payload,
+ signingCredentials,
+ encryptingCredentials,
+ compressionAlgorithm,
+ additionalHeaderClaims,
+ additionalInnerHeaderClaims,
+ null);
+ }
+
+ ///
+ /// Creates a JWE (Json Web Encryption).
+ ///
+ /// A string containing JSON which represents the JWT token payload.
+ /// Defines the security key and algorithm that will be used to sign the JWT.
+ /// Defines the security key and algorithm that will be used to encrypt the JWT.
+ /// Defines the compression algorithm that will be used to compress the JWT token payload.
+ /// Defines the dictionary containing any custom header claims that need to be added to the outer JWT token header.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if is null.
+ /// if ,
+ /// , , and/or
+ /// are present inside of .
+ /// A JWE in compact serialization format.
+ public virtual string CreateToken(
+ string payload,
+ SigningCredentials signingCredentials,
+ EncryptingCredentials encryptingCredentials,
+ string compressionAlgorithm,
+ IDictionary additionalHeaderClaims)
+ {
+ if (string.IsNullOrEmpty(payload))
+ throw LogHelper.LogArgumentNullException(nameof(payload));
+
+ if (string.IsNullOrEmpty(compressionAlgorithm))
+ throw LogHelper.LogArgumentNullException(nameof(compressionAlgorithm));
+
+ _ = signingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(signingCredentials));
+ _ = encryptingCredentials ?? throw LogHelper.LogArgumentNullException(nameof(encryptingCredentials));
+ _ = additionalHeaderClaims ?? throw LogHelper.LogArgumentNullException(nameof(additionalHeaderClaims));
+
+ return CreateToken(
+ payload,
+ signingCredentials,
+ encryptingCredentials,
+ compressionAlgorithm,
+ additionalHeaderClaims,
+ null,
+ null);
+ }
+
+ internal static string CreateToken
+ (
+ string payload,
+ SigningCredentials signingCredentials,
+ EncryptingCredentials encryptingCredentials,
+ string compressionAlgorithm,
+ IDictionary additionalHeaderClaims,
+ IDictionary additionalInnerHeaderClaims,
+ string tokenType)
+ {
+ using (MemoryStream utf8ByteMemoryStream = new ())
+ {
+ Utf8JsonWriter writer = null;
+ char[] encodedChars = null;
+ byte[] asciiBytes = null;
+ byte[] signatureBytes = null;
+ byte[] payloadBytes = null;
+
+ try
+ {
+ writer = new Utf8JsonWriter(utf8ByteMemoryStream, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
+
+ WriteJwsHeader(
+ ref writer,
+ signingCredentials,
+ encryptingCredentials,
+ additionalHeaderClaims,
+ additionalInnerHeaderClaims,
+ null);
+
+ // mark length of jwt header
+ int headerLength = (int)utf8ByteMemoryStream.Length;
+ int signatureSize = 0;
+ if (signingCredentials != null)
+ signatureSize = SupportedAlgorithms.GetMaxByteCount(signingCredentials.Algorithm);
+
+ payloadBytes = ArrayPool.Shared.Rent(Encoding.UTF8.GetMaxByteCount(payload.Length));
+ int payloadSize = Encoding.UTF8.GetBytes(payload, 0, payload.Length, payloadBytes, 0);
+
+ int encodedBufferSize = (headerLength + payloadSize + 4 + signatureSize) / 3 * 4;
+ encodedChars = ArrayPool.Shared.Rent(encodedBufferSize + 4);
+
+ int sizeOfEncodedHeader = Base64UrlEncoder.Encode(utf8ByteMemoryStream.GetBuffer().AsSpan(0, headerLength), encodedChars);
+ encodedChars[sizeOfEncodedHeader] = '.';
+
+ int sizeOfEncodedPayload = Base64UrlEncoder.Encode(payloadBytes.AsSpan(0, payloadSize), encodedChars.AsSpan(sizeOfEncodedHeader + 1));
+ // encodeChars => 'EncodedHeader.EncodedPayload'
+
+ // Get ASCII Bytes of 'EncodedHeader.EncodedPayload' which is used to calculate the signature
+ asciiBytes = ArrayPool.Shared.Rent(Encoding.ASCII.GetMaxByteCount(encodedBufferSize));
+ int sizeOfEncodedHeaderAndPayloadAsciiBytes
+ = Encoding.ASCII.GetBytes(encodedChars, 0, sizeOfEncodedHeader + sizeOfEncodedPayload + 1, asciiBytes, 0);
+
+ encodedChars[sizeOfEncodedHeader + sizeOfEncodedPayload + 1] = '.';
+ // encodedChars => 'EncodedHeader.EncodedPayload.'
+
+ int sizeOfEncodedSignature = 0;
+ if (signingCredentials != null)
+ {
+#if NET6_0_OR_GREATER
+ signatureBytes = ArrayPool.Shared.Rent(signatureSize);
+ bool signatureSucceeded = JwtTokenUtilities.CreateSignature(
+ asciiBytes.AsSpan(0, sizeOfEncodedHeaderAndPayloadAsciiBytes),
+ signatureBytes,
+ signingCredentials,
+ out int signatureLength);
+#else
+ signatureBytes = JwtTokenUtilities.CreateEncodedSignature(asciiBytes, 0, sizeOfEncodedHeaderAndPayloadAsciiBytes, signingCredentials);
+ int signatureLength = signatureBytes.Length;
+#endif
+ sizeOfEncodedSignature = Base64UrlEncoder.Encode(signatureBytes.AsSpan(0, signatureLength), encodedChars.AsSpan(sizeOfEncodedHeader + sizeOfEncodedPayload + 2));
+ }
+
+ if (encryptingCredentials != null)
+ {
+ return EncryptToken(
+ Encoding.UTF8.GetBytes(encodedChars, 0, sizeOfEncodedHeader + sizeOfEncodedPayload + sizeOfEncodedSignature + 2),
+ encryptingCredentials,
+ compressionAlgorithm,
+ additionalHeaderClaims,
+ tokenType);
+ }
+ else
+ {
+ return encodedChars.AsSpan(0, sizeOfEncodedHeader + sizeOfEncodedPayload + sizeOfEncodedSignature + 2).ToString();
+ }
+ }
+ finally
+ {
+ if (encodedChars is not null)
+ ArrayPool.Shared.Return(encodedChars);
+#if NET6_0_OR_GREATER
+ if (signatureBytes is not null)
+ ArrayPool.Shared.Return(signatureBytes);
+#endif
+ if (asciiBytes is not null)
+ ArrayPool.Shared.Return(asciiBytes);
+
+ if (payloadBytes is not null)
+ ArrayPool.Shared.Return(payloadBytes);
+
+ writer?.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// A can contain claims from multiple locations.
+ /// This method consolidates the claims and adds default times {exp, iat, nbf} if needed.
+ ///
+ /// The to use.
+ /// The used to create the token.
+ /// A boolean that controls if expiration, notbefore, issuedat should be added if missing.
+ /// The default value for the token lifetime in minutes.
+ /// A dictionary of claims.
+ internal static void WriteJwsPayload(
+ ref Utf8JsonWriter writer,
+ SecurityTokenDescriptor tokenDescriptor,
+ bool setDefaultTimesOnTokenCreation,
+ int tokenLifetimeInMinutes)
+ {
+ bool audienceChecked = false;
+ bool audienceSet = false;
+ bool issuerChecked = false;
+ bool issuerSet = false;
+ bool expChecked = false;
+ bool expSet = false;
+ bool iatChecked = false;
+ bool iatSet = false;
+ bool nbfChecked = false;
+ bool nbfSet = false;
+
+ writer.WriteStartObject();
+
+ if (!string.IsNullOrEmpty(tokenDescriptor.Audience))
+ {
+ audienceSet = true;
+ writer.WritePropertyName(JwtPayloadUtf8Bytes.Aud);
+ writer.WriteStringValue(tokenDescriptor.Audience);
+ }
+
+ if (!string.IsNullOrEmpty(tokenDescriptor.Issuer))
+ {
+ issuerSet = true;
+ writer.WritePropertyName(JwtPayloadUtf8Bytes.Iss);
+ writer.WriteStringValue(tokenDescriptor.Issuer);
+ }
+
+ if (tokenDescriptor.Expires.HasValue)
+ {
+ expSet = true;
+ writer.WritePropertyName(JwtPayloadUtf8Bytes.Exp);
+ writer.WriteNumberValue(EpochTime.GetIntDate(tokenDescriptor.Expires.Value));
+ }
+
+ if (tokenDescriptor.IssuedAt.HasValue)
+ {
+ iatSet = true;
+ writer.WritePropertyName(JwtPayloadUtf8Bytes.Iat);
+ writer.WriteNumberValue(EpochTime.GetIntDate(tokenDescriptor.IssuedAt.Value));
+ }
+
+ if (tokenDescriptor.NotBefore.HasValue)
+ {
+ nbfSet = true;
+ writer.WritePropertyName(JwtPayloadUtf8Bytes.Nbf);
+ writer.WriteNumberValue(EpochTime.GetIntDate(tokenDescriptor.NotBefore.Value));
+ }
+
+ // Duplicates are resolved according to the following priority:
+ // SecurityTokenDescriptor.{Audience, Issuer, Expires, IssuedAt, NotBefore}, SecurityTokenDescriptor.Claims, SecurityTokenDescriptor.Subject.Claims
+ // SecurityTokenDescriptor.Claims are KeyValuePairs, whereas SecurityTokenDescriptor.Subject.Claims are System.Security.Claims.Claim and are processed differently.
+
+ if (tokenDescriptor.Claims != null && tokenDescriptor.Claims.Count > 0)
+ {
+ foreach (KeyValuePair kvp in tokenDescriptor.Claims)
+ {
+ if (!audienceChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Aud, StringComparison.Ordinal))
+ {
+ audienceChecked = true;
+ if (audienceSet)
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogHelper.FormatInvariant(LogMessages.IDX14113, LogHelper.MarkAsNonPII(nameof(tokenDescriptor.Audience))));
+
+ continue;
+ }
+
+ audienceSet = true;
+ }
+
+ if (!issuerChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Iss, StringComparison.Ordinal))
+ {
+ issuerChecked = true;
+ if (issuerSet)
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogHelper.FormatInvariant(LogMessages.IDX14113, LogHelper.MarkAsNonPII(nameof(tokenDescriptor.Issuer))));
+
+ continue;
+ }
+
+ issuerSet = true;
+ }
+
+ if (!expChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Exp, StringComparison.Ordinal))
+ {
+ expChecked = true;
+ if (expSet)
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogHelper.FormatInvariant(LogMessages.IDX14113, LogHelper.MarkAsNonPII(nameof(tokenDescriptor.Expires))));
+
+ continue;
+ }
+
+ expSet = true;
+ }
+
+ if (!iatChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Iat, StringComparison.Ordinal))
+ {
+ iatChecked = true;
+ if (iatSet)
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogHelper.FormatInvariant(LogMessages.IDX14113, LogHelper.MarkAsNonPII(nameof(tokenDescriptor.Expires))));
+
+ continue;
+ }
+
+ iatSet = true;
+ }
+
+ if (!nbfChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Nbf, StringComparison.Ordinal))
+ {
+ nbfChecked = true;
+ if (nbfSet)
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogHelper.FormatInvariant(LogMessages.IDX14113, LogHelper.MarkAsNonPII(nameof(tokenDescriptor.Expires))));
+
+ continue;
+ }
+
+ nbfSet = true;
+ }
+
+ JsonPrimitives.WriteObject(ref writer, kvp.Key, kvp.Value);
+ }
+ }
+
+ AddSubjectClaims(ref writer, tokenDescriptor, audienceSet, issuerSet, ref expSet, ref iatSet, ref nbfSet);
+
+ // By default we set these three properties only if they haven't been detected before.
+ if (setDefaultTimesOnTokenCreation && !(expSet && iatSet && nbfSet))
+ {
+ DateTime now = DateTime.UtcNow;
+
+ if (!expSet)
+ {
+ writer.WritePropertyName(JwtPayloadUtf8Bytes.Exp);
+ writer.WriteNumberValue(EpochTime.GetIntDate(now + TimeSpan.FromMinutes(tokenLifetimeInMinutes)));
+ }
+
+ if (!iatSet)
+ {
+ writer.WritePropertyName(JwtPayloadUtf8Bytes.Iat);
+ writer.WriteNumberValue(EpochTime.GetIntDate(now));
+ }
+
+ if (!nbfSet)
+ {
+ writer.WritePropertyName(JwtPayloadUtf8Bytes.Nbf);
+ writer.WriteNumberValue(EpochTime.GetIntDate(now));
+ }
+ }
+
+ writer.WriteEndObject();
+ writer.Flush();
+ }
+
+ internal static void AddSubjectClaims(
+ ref Utf8JsonWriter writer,
+ SecurityTokenDescriptor tokenDescriptor,
+ bool audienceSet,
+ bool issuerSet,
+ ref bool expSet,
+ ref bool iatSet,
+ ref bool nbfSet)
+ {
+ if (tokenDescriptor.Subject == null)
+ return;
+
+ bool expReset = false;
+ bool iatReset = false;
+ bool nbfReset = false;
+
+ var payload = new Dictionary();
+
+ bool checkClaims = tokenDescriptor.Claims != null && tokenDescriptor.Claims.Count > 0;
+
+ foreach (Claim claim in tokenDescriptor.Subject.Claims)
+ {
+ if (claim == null)
+ continue;
+
+ // skipping these as they have been added by values in the SecurityTokenDescriptor
+ if (checkClaims && tokenDescriptor.Claims.ContainsKey(claim.Type))
+ continue;
+
+ if (audienceSet && claim.Type.Equals(JwtRegisteredClaimNames.Aud, StringComparison.Ordinal))
+ continue;
+
+ if (issuerSet && claim.Type.Equals(JwtRegisteredClaimNames.Iss, StringComparison.Ordinal))
+ continue;
+
+ if (claim.Type.Equals(JwtRegisteredClaimNames.Exp, StringComparison.Ordinal))
+ {
+ if (expSet)
+ continue;
+
+ expReset = true;
+ }
+
+ if (claim.Type.Equals(JwtRegisteredClaimNames.Iat, StringComparison.Ordinal))
+ {
+ if (iatSet)
+ continue;
+
+ iatReset = true;
+ }
+
+ if (claim.Type.Equals(JwtRegisteredClaimNames.Nbf, StringComparison.Ordinal))
+ {
+ if (nbfSet)
+ continue;
+
+ nbfReset = true;
+ }
+
+ object jsonClaimValue = claim.ValueType.Equals(ClaimValueTypes.String) ? claim.Value : TokenUtilities.GetClaimValueUsingValueType(claim);
+
+ // The enumeration is from ClaimsIdentity.Claims, there can be duplicates.
+ // When a duplicate is detected, we create a List and add both to a list.
+ // When the creating the JWT and a list is found, a JsonArray will be created.
+ if (payload.TryGetValue(claim.Type, out object existingValue))
+ {
+ if (existingValue is List