diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs
index 7c7d63907e..a253ffd74e 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.CreateToken.cs
@@ -835,7 +835,11 @@ internal static void WriteJwsPayload(
             // By default we set these three properties only if they haven't been detected before.
             if (setDefaultTimesOnTokenCreation && !(expSet && iatSet && nbfSet))
             {
-                DateTime now = DateTime.UtcNow;
+                DateTime now =
+#if SUPPORTS_TIME_PROVIDER
+                    tokenDescriptor.TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow;
 
                 if (!expSet)
                 {
diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectProtocolValidator.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectProtocolValidator.cs
index 7408ce1bfb..e178738d9c 100644
--- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectProtocolValidator.cs
+++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectProtocolValidator.cs
@@ -88,12 +88,30 @@ public virtual string GenerateNonce()
             string nonce = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString() + Guid.NewGuid().ToString()));
             if (RequireTimeStampInNonce)
             {
-                return DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture) + "." + nonce;
+                DateTime utcNow =
+#if SUPPORTS_TIME_PROVIDER
+                    TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow;
+
+                return utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + "." + nonce;
             }
 
             return nonce;
         }
 
+#if SUPPORTS_TIME_PROVIDER
+#nullable enable
+        /// <summary>
+        /// Gets or sets the time provider.
+        /// </summary>
+        /// <remarks>
+        /// If not set, fall back to using the <see cref="DateTime"/> class to obtain the current time.
+        /// </remarks>
+        public TimeProvider? TimeProvider { get; set; }
+#nullable restore
+#endif
+
         /// <summary>
         /// Gets the algorithm mapping between OpenIdConnect and .Net for Hash algorithms.
         /// a <see cref="IDictionary{TKey, TValue}"/> that contains mappings from the JWT namespace <see href="https://datatracker.ietf.org/doc/html/rfc7518"/> to .NET.
@@ -658,7 +676,12 @@ protected virtual void ValidateNonce(OpenIdConnectProtocolValidationContext vali
                     throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21327, LogHelper.MarkAsNonPII(timestamp), LogHelper.MarkAsNonPII(DateTime.MinValue.Ticks.ToString(CultureInfo.InvariantCulture)), LogHelper.MarkAsNonPII(DateTime.MaxValue.Ticks.ToString(CultureInfo.InvariantCulture))), ex));
                 }
 
-                DateTime utcNow = DateTime.UtcNow;
+                DateTime utcNow =
+#if SUPPORTS_TIME_PROVIDER
+                    TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow;
+
                 if (nonceTime + NonceLifetime < utcNow)
                     throw LogHelper.LogExceptionMessage(new OpenIdConnectProtocolInvalidNonceException(LogHelper.FormatInvariant(LogMessages.IDX21324, nonceFoundInJwt, LogHelper.MarkAsNonPII(nonceTime.ToString(CultureInfo.InvariantCulture)), LogHelper.MarkAsNonPII(utcNow.ToString(CultureInfo.InvariantCulture)), LogHelper.MarkAsNonPII(NonceLifetime.ToString("c", CultureInfo.InvariantCulture)))));
             }
diff --git a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestCreationParameters.cs b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestCreationParameters.cs
index 781d56e2c5..56fb919ba0 100644
--- a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestCreationParameters.cs
+++ b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestCreationParameters.cs
@@ -14,7 +14,7 @@ public class SignedHttpRequestCreationParameters
         /// Gets or sets a value indicating whether the <see cref="ConfirmationClaimTypes.Cnf"/> claim should be created and added or not.
         /// </summary>
         /// <remarks>
-        /// <see cref="SignedHttpRequestDescriptor.CnfClaimValue"/> will be used as a "cnf" claim value, if set. 
+        /// <see cref="SignedHttpRequestDescriptor.CnfClaimValue"/> will be used as a "cnf" claim value, if set.
         /// Otherwise, a "cnf" claim value will be derived from <see cref="SignedHttpRequestDescriptor.SigningCredentials"/>.
         /// </remarks>
         public bool CreateCnf { get; set; } = true;
@@ -27,19 +27,19 @@ public class SignedHttpRequestCreationParameters
         /// <summary>
         /// Gets or sets a value indicating whether the <see cref="SignedHttpRequestClaimTypes.Ts"/> claim should be created and added or not.
         /// </summary>
-        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks> 
+        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks>
         public bool CreateTs { get; set; } = true;
 
         /// <summary>
         /// Gets or sets a value indicating whether the <see cref="SignedHttpRequestClaimTypes.M"/> claim should be created and added or not.
         /// </summary>
-        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks> 
+        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks>
         public bool CreateM { get; set; } = true;
 
         /// <summary>
         /// Gets or sets a value indicating whether the <see cref="SignedHttpRequestClaimTypes.U"/> claim should be created and added or not.
         /// </summary>
-        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks> 
+        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks>
         public bool CreateU { get; set; } = true;
 
         /// <summary>
@@ -51,19 +51,19 @@ public class SignedHttpRequestCreationParameters
         /// <summary>
         /// Gets or sets a value indicating whether the <see cref="SignedHttpRequestClaimTypes.Q"/> claim should be created and added or not.
         /// </summary>
-        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks> 
+        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks>
         public bool CreateQ { get; set; }
 
         /// <summary>
         /// Gets or sets a value indicating whether the <see cref="SignedHttpRequestClaimTypes.H"/> claim should be created and added or not.
         /// </summary>
-        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks> 
+        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks>
         public bool CreateH { get; set; }
 
         /// <summary>
         /// Gets or sets a value indicating whether the <see cref="SignedHttpRequestClaimTypes.B"/> claim should be created and added or not.
         /// </summary>
-        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks> 
+        /// <remarks>https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3</remarks>
         public bool CreateB { get; set; }
 
         /// <summary>
@@ -76,5 +76,17 @@ public class SignedHttpRequestCreationParameters
         /// </summary>
         /// <remarks>Allows for adjusting the local time so it matches a server time.</remarks>
         public TimeSpan TimeAdjustment { get; set; } = DefaultTimeAdjustment;
+
+#if SUPPORTS_TIME_PROVIDER
+#nullable enable
+        /// <summary>
+        /// Gets or sets the time provider.
+        /// </summary>
+        /// <remarks>
+        /// If not set, fall back to using the <see cref="DateTime"/> class to obtain the current time.
+        /// </remarks>
+        public TimeProvider? TimeProvider { get; set; }
+#nullable restore
+#endif
     }
 }
diff --git a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestHandler.cs b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestHandler.cs
index 1411d14732..0b366aa2dc 100644
--- a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestHandler.cs
+++ b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestHandler.cs
@@ -21,7 +21,7 @@
 namespace Microsoft.IdentityModel.Protocols.SignedHttpRequest
 {
     /// <summary>
-    /// A handler designed for creating and validating signed http requests. 
+    /// A handler designed for creating and validating signed http requests.
     /// </summary>
     /// <remarks>The handler implementation is based on 'A Method for Signing HTTP Requests for OAuth' specification.</remarks>
     public class SignedHttpRequestHandler
@@ -143,7 +143,7 @@ public string CreateSignedHttpRequest(SignedHttpRequestDescriptor signedHttpRequ
         /// Creates a JSON representation of a HttpRequest payload.
         /// </summary>
         /// <param name="signedHttpRequestDescriptor">A structure that wraps parameters needed for SignedHttpRequest creation.</param>
-        /// <param name="callContext" >An opaque context used to store work and logs when working with authentication artifacts.</param> 
+        /// <param name="callContext" >An opaque context used to store work and logs when working with authentication artifacts.</param>
         /// <returns>A JSON representation of an HttpRequest payload.</returns>
         /// <remarks>
         /// Users can utilize <see cref="SignedHttpRequestDescriptor.AdditionalPayloadClaims"/> to create additional claim(s) and add them to the signed http request.
@@ -227,13 +227,19 @@ internal virtual void AddAtClaim(ref Utf8JsonWriter writer, SignedHttpRequestDes
         /// <param name="signedHttpRequestDescriptor">A structure that wraps parameters needed for SignedHttpRequest creation.</param>
         /// <remarks>
         /// This method will be executed only if <see cref="SignedHttpRequestCreationParameters.CreateTs"/> is set to <c>true</c>.
-        /// </remarks>    
+        /// </remarks>
         internal virtual void AddTsClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor)
         {
+            DateTime utcNow =
+#if SUPPORTS_TIME_PROVIDER
+                signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                DateTime.UtcNow;
+
             writer.WriteNumber(
                 SignedHttpRequestClaimTypes.Ts,
                 EpochTime.GetIntDate(
-                    DateTime.UtcNow.Add(signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.TimeAdjustment)));
+                    utcNow.Add(signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.TimeAdjustment)));
         }
 
         /// <summary>
@@ -243,7 +249,7 @@ internal virtual void AddTsClaim(ref Utf8JsonWriter writer, SignedHttpRequestDes
         /// <param name="signedHttpRequestDescriptor">A structure that wraps parameters needed for SignedHttpRequest creation.</param>
         /// <remarks>
         /// This method will be executed only if <see cref="SignedHttpRequestCreationParameters.CreateM"/> is set to <c>true</c>.
-        /// </remarks>   
+        /// </remarks>
         internal virtual void AddMClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor)
         {
             if (string.IsNullOrEmpty(signedHttpRequestDescriptor.HttpRequestData.Method))
@@ -263,7 +269,7 @@ internal virtual void AddMClaim(ref Utf8JsonWriter writer, SignedHttpRequestDesc
         /// <param name="signedHttpRequestDescriptor">A structure that wraps parameters needed for SignedHttpRequest creation.</param>
         /// <remarks>
         /// This method will be executed only if <see cref="SignedHttpRequestCreationParameters.CreateU"/> is set to <c>true</c>.
-        /// </remarks>  
+        /// </remarks>
         internal virtual void AddUClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor)
         {
             if (signedHttpRequestDescriptor.HttpRequestData.Uri == null)
@@ -289,7 +295,7 @@ internal virtual void AddUClaim(ref Utf8JsonWriter writer, SignedHttpRequestDesc
         /// <param name="signedHttpRequestDescriptor">A structure that wraps parameters needed for SignedHttpRequest creation.</param>
         /// <remarks>
         /// This method will be executed only if <see cref="SignedHttpRequestCreationParameters.CreateP"/> is set to <c>true</c>.
-        /// </remarks>  
+        /// </remarks>
         internal virtual void AddPClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor)
         {
             if (signedHttpRequestDescriptor.HttpRequestData.Uri == null)
@@ -351,7 +357,7 @@ internal virtual void AddQClaim(ref Utf8JsonWriter writer, SignedHttpRequestDesc
         /// <param name="signedHttpRequestDescriptor">A structure that wraps parameters needed for SignedHttpRequest creation.</param>
         /// <remarks>
         /// This method will be executed only if <see cref="SignedHttpRequestCreationParameters.CreateH"/> is set to <c>true</c>.
-        /// </remarks>  
+        /// </remarks>
         internal void AddHClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor)
         {
             IDictionary<string, string> sanitizedHeaders = SanitizeHeaders(signedHttpRequestDescriptor.HttpRequestData.Headers);
@@ -389,7 +395,7 @@ internal void AddHClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor s
         /// <param name="signedHttpRequestDescriptor">A structure that wraps parameters needed for SignedHttpRequest creation.</param>
         /// <remarks>
         /// This method will be executed only if <see cref="SignedHttpRequestCreationParameters.CreateB"/> is set to <c>true</c>.
-        /// </remarks> 
+        /// </remarks>
         internal virtual void AddBClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor)
         {
             try
@@ -442,7 +448,7 @@ internal virtual void AddCnfClaim(ref Utf8JsonWriter writer, SignedHttpRequestDe
                     JsonWebKey jsonWebKey;
                     if (signedHttpRequestDescriptor.SigningCredentials.Key is JsonWebKey jwk)
                         jsonWebKey = jwk;
-                    // create a JsonWebKey from an X509SecurityKey, represented as an RsaSecurityKey. 
+                    // create a JsonWebKey from an X509SecurityKey, represented as an RsaSecurityKey.
                     else if (signedHttpRequestDescriptor.SigningCredentials.Key is X509SecurityKey x509SecurityKey)
                         jsonWebKey = JsonWebKeyConverter.ConvertFromX509SecurityKey(x509SecurityKey, true);
                     else if (signedHttpRequestDescriptor.SigningCredentials.Key is AsymmetricSecurityKey asymmetricSecurityKey)
@@ -480,7 +486,7 @@ internal virtual void AddCnfClaim(ref Utf8JsonWriter writer, SignedHttpRequestDe
         /// </summary>
         /// <param name="signedHttpRequestValidationContext">A structure that wraps parameters needed for SignedHttpRequest validation.</param>
         /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
-        /// <returns>A <see cref="SignedHttpRequestValidationResult"/>. 
+        /// <returns>A <see cref="SignedHttpRequestValidationResult"/>.
         /// <see cref="TokenValidationResult.IsValid"/> will be <c>true</c> if the signed http request was successfully validated, <c>false</c> otherwise.
         /// </returns>
         public async Task<SignedHttpRequestValidationResult> ValidateSignedHttpRequestAsync(SignedHttpRequestValidationContext signedHttpRequestValidationContext, CancellationToken cancellationToken)
@@ -725,7 +731,12 @@ internal virtual void ValidateTsClaim(JsonWebToken signedHttpRequest, SignedHttp
             if (!signedHttpRequest.TryGetPayloadValue(SignedHttpRequestClaimTypes.Ts, out long tsClaimValue))
                 throw LogHelper.LogExceptionMessage(new SignedHttpRequestInvalidTsClaimException(LogHelper.FormatInvariant(LogMessages.IDX23003, LogHelper.MarkAsNonPII(SignedHttpRequestClaimTypes.Ts))));
 
-            DateTime utcNow = DateTime.UtcNow;
+            DateTime utcNow =
+#if SUPPORTS_TIME_PROVIDER
+                signedHttpRequestValidationContext.AccessTokenValidationParameters.TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                DateTime.UtcNow;
+
             DateTime signedHttpRequestCreationTime = EpochTime.DateTime(tsClaimValue);
             DateTime signedHttpRequestExpirationTime = signedHttpRequestCreationTime.Add(signedHttpRequestValidationContext.SignedHttpRequestValidationParameters.SignedHttpRequestLifetime);
 
@@ -762,7 +773,7 @@ internal virtual void ValidateMClaim(JsonWebToken signedHttpRequest, SignedHttpR
         }
 
         /// <summary>
-        /// Validates the signed http request "u" claim. 
+        /// Validates the signed http request "u" claim.
         /// </summary>
         /// <param name="signedHttpRequest">A SignedHttpRequest.</param>
         /// <param name="signedHttpRequestValidationContext">A structure that wraps parameters needed for SignedHttpRequest validation.</param>
@@ -796,7 +807,7 @@ internal virtual void ValidateUClaim(JsonWebToken signedHttpRequest, SignedHttpR
         }
 
         /// <summary>
-        /// Validates the signed http request "p" claim. 
+        /// Validates the signed http request "p" claim.
         /// </summary>
         /// <param name="signedHttpRequest">A SignedHttpRequest.</param>
         /// <param name="signedHttpRequestValidationContext">A structure that wraps parameters needed for SignedHttpRequest validation.</param>
@@ -825,7 +836,7 @@ internal virtual void ValidatePClaim(JsonWebToken signedHttpRequest, SignedHttpR
         }
 
         /// <summary>
-        /// Validates the signed http request "q" claim. 
+        /// Validates the signed http request "q" claim.
         /// </summary>
         /// <param name="signedHttpRequest">A SignedHttpRequest.</param>
         /// <param name="signedHttpRequestValidationContext">A structure that wraps parameters needed for SignedHttpRequest validation.</param>
@@ -900,7 +911,7 @@ internal virtual void ValidateQClaim(JsonWebToken signedHttpRequest, SignedHttpR
         }
 
         /// <summary>
-        /// Validates the signed http request "h" claim. 
+        /// Validates the signed http request "h" claim.
         /// </summary>
         /// <param name="signedHttpRequest">A SignedHttpRequest.</param>
         /// <param name="signedHttpRequestValidationContext">A structure that wraps parameters needed for SignedHttpRequest validation.</param>
@@ -969,7 +980,7 @@ internal virtual void ValidateHClaim(JsonWebToken signedHttpRequest, SignedHttpR
         }
 
         /// <summary>
-        /// Validates the signed http request "b" claim. 
+        /// Validates the signed http request "b" claim.
         /// </summary>
         /// <param name="signedHttpRequest">A SignedHttpRequest.</param>
         /// <param name="signedHttpRequestValidationContext">A structure that wraps parameters needed for SignedHttpRequest validation.</param>
@@ -1082,7 +1093,7 @@ internal virtual async Task<SecurityKey> ResolvePopKeyFromCnfClaimAsync(Cnf cnf,
         }
 
         /// <summary>
-        /// Resolves a PoP <see cref="SecurityKey"/> from the asymmetric representation of a PoP key. 
+        /// Resolves a PoP <see cref="SecurityKey"/> from the asymmetric representation of a PoP key.
         /// </summary>
         /// <param name="jsonWebKey">The JsonWebKey to resolve.</param>
         /// <param name="signedHttpRequestValidationContext">A structure that wraps parameters needed for SignedHttpRequest validation.</param>
@@ -1103,7 +1114,7 @@ internal virtual SecurityKey ResolvePopKeyFromJwk(JsonWebKey jsonWebKey, SignedH
         }
 
         /// <summary>
-        /// Resolves a PoP <see cref="SecurityKey"/> from the encrypted symmetric representation of a PoP key. 
+        /// Resolves a PoP <see cref="SecurityKey"/> from the encrypted symmetric representation of a PoP key.
         /// </summary>
         /// <param name="jwe">An encrypted symmetric representation of a PoP key (JSON).</param>
         /// <param name="signedHttpRequestValidationContext">A structure that wraps parameters needed for SignedHttpRequest validation.</param>
@@ -1119,7 +1130,7 @@ internal virtual async Task<SecurityKey> ResolvePopKeyFromJweAsync(string jwe, S
         }
 
         /// <summary>
-        /// Resolves a PoP <see cref="SecurityKey"/> from the URL reference to a PoP key.  
+        /// Resolves a PoP <see cref="SecurityKey"/> from the URL reference to a PoP key.
         /// </summary>
         /// <param name="jkuSetUrl">A URL reference to a PoP JWK set.</param>
         /// <param name="cnf">A confirmation ("cnf") claim as a JObject.</param>
@@ -1203,7 +1214,7 @@ internal virtual async Task<IList<SecurityKey>> GetPopKeysFromJkuAsync(string jk
         }
 
         /// <summary>
-        /// Resolves a PoP <see cref="SecurityKey"/> using a key identifier of a PoP key. 
+        /// Resolves a PoP <see cref="SecurityKey"/> using a key identifier of a PoP key.
         /// </summary>
         /// <param name="kid">A <see cref="ConfirmationClaimTypes.Kid"/> claim value.</param>
         /// <param name="signedHttpRequest">A signed http request as a JWT.</param>
diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
index 3ab4c16aef..37e11c5431 100644
--- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
+++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
@@ -190,8 +190,14 @@ public virtual async Task<T> GetConfigurationAsync(CancellationToken cancel)
                                         result.ErrorMessage)));
                     }
 
+                    DateTimeOffset utcNow =
+#if SUPPORTS_TIME_PROVIDER
+                        TimeProvider?.GetUtcNow() ??
+#endif
+                        DateTimeOffset.UtcNow;
+
                     // Add a random amount between 0 and 5% of AutomaticRefreshInterval jitter to avoid spike traffic to IdentityProvider.
-                    _syncAfter = DateTimeUtil.Add(DateTime.UtcNow, AutomaticRefreshInterval +
+                    _syncAfter = DateTimeUtil.Add(utcNow.UtcDateTime, AutomaticRefreshInterval +
                         TimeSpan.FromSeconds(new Random().Next((int)AutomaticRefreshInterval.TotalSeconds / 20)));
 
                     _currentConfiguration = configuration;
@@ -200,6 +206,12 @@ public virtual async Task<T> GetConfigurationAsync(CancellationToken cancel)
                 {
                     fetchMetadataFailure = ex;
 
+                    DateTime utcNow =
+#if SUPPORTS_TIME_PROVIDER
+                            TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                        DateTime.UtcNow;
+
                     // In this case configuration was never obtained.
                     if (_currentConfiguration == null)
                     {
@@ -208,12 +220,12 @@ public virtual async Task<T> GetConfigurationAsync(CancellationToken cancel)
                             // Adopt exponential backoff for bootstrap refresh interval with a decorrelated jitter if it is not longer than the refresh interval.
                             TimeSpan _bootstrapRefreshIntervalWithJitter = TimeSpan.FromSeconds(new Random().Next((int)_bootstrapRefreshInterval.TotalSeconds));
                             _bootstrapRefreshInterval += _bootstrapRefreshInterval;
-                            _syncAfter = DateTimeUtil.Add(DateTime.UtcNow, _bootstrapRefreshIntervalWithJitter);
+                            _syncAfter = DateTimeUtil.Add(utcNow, _bootstrapRefreshIntervalWithJitter);
                         }
                         else
                         {
                             _syncAfter = DateTimeUtil.Add(
-                                DateTime.UtcNow,
+                                utcNow,
                                 AutomaticRefreshInterval < RefreshInterval ? AutomaticRefreshInterval : RefreshInterval);
                         }
 
@@ -229,7 +241,7 @@ public virtual async Task<T> GetConfigurationAsync(CancellationToken cancel)
                     else
                     {
                         _syncAfter = DateTimeUtil.Add(
-                            DateTime.UtcNow,
+                            utcNow,
                             AutomaticRefreshInterval < RefreshInterval ? AutomaticRefreshInterval : RefreshInterval);
 
                         LogHelper.LogExceptionMessage(
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs
index d82ff3f75e..6828da5487 100644
--- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs
@@ -70,7 +70,7 @@ public IEqualityComparer<SamlSubject> SamlSubjectEqualityComparer
         /// <summary>
         /// Gets or set the <see cref="SamlSerializer"/> that will be used to read and write a <see cref="SamlSecurityToken"/>.
         /// </summary>
-        /// <exception cref="ArgumentNullException">'value' is null.</exception> 
+        /// <exception cref="ArgumentNullException">'value' is null.</exception>
         public SamlSerializer Serializer
         {
             get { return _serializer; }
@@ -186,7 +186,7 @@ protected virtual ICollection<SamlAttribute> ConsolidateAttributes(ICollection<S
         }
 
         /// <summary>
-        /// Override this method to provide a SamlAdvice to place in the Samltoken. 
+        /// Override this method to provide a SamlAdvice to place in the Samltoken.
         /// </summary>
         /// <param name="tokenDescriptor">Contains information about the token.</param>
         /// <returns>SamlAdvice, default is null.</returns>
@@ -286,7 +286,7 @@ protected virtual SamlAttributeStatement CreateAttributeStatement(SamlSubject su
         }
 
         /// <summary>
-        /// Creates a SamlAuthenticationStatement for each AuthenticationInformation found in AuthenticationInformation. 
+        /// Creates a SamlAuthenticationStatement for each AuthenticationInformation found in AuthenticationInformation.
         /// Override this method to provide a custom implementation.
         /// </summary>
         /// <param name="subject">The SamlSubject of the Statement.</param>
@@ -360,12 +360,20 @@ protected virtual SamlConditions CreateConditions(SecurityTokenDescriptor tokenD
             if (tokenDescriptor.NotBefore.HasValue)
                 conditions.NotBefore = tokenDescriptor.NotBefore.Value;
             else if (SetDefaultTimesOnTokenCreation)
-                conditions.NotBefore = DateTime.UtcNow;
+                conditions.NotBefore =
+#if SUPPORTS_TIME_PROVIDER
+                    tokenDescriptor.TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow;
 
             if (tokenDescriptor.Expires.HasValue)
                 conditions.NotOnOrAfter = tokenDescriptor.Expires.Value;
             else if (SetDefaultTimesOnTokenCreation)
-                conditions.NotOnOrAfter = DateTime.UtcNow + TimeSpan.FromMinutes(TokenLifetimeInMinutes);
+                conditions.NotOnOrAfter =
+#if SUPPORTS_TIME_PROVIDER
+                    tokenDescriptor.TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow + TimeSpan.FromMinutes(TokenLifetimeInMinutes);
 
             if (tokenDescriptor.Audiences.Count > 0)
             {
@@ -516,7 +524,12 @@ public virtual SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor
             var conditions = CreateConditions(tokenDescriptor);
             var advice = CreateAdvice(tokenDescriptor);
 
-            var issuedAt = tokenDescriptor.IssuedAt.HasValue ? tokenDescriptor.IssuedAt.Value : DateTime.UtcNow;
+            var issuedAt = tokenDescriptor.IssuedAt.HasValue ? tokenDescriptor.IssuedAt.Value :
+#if SUPPORTS_TIME_PROVIDER
+                tokenDescriptor.TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                DateTime.UtcNow;
+
             return new SamlSecurityToken(new SamlAssertion("_" + Guid.NewGuid().ToString(), tokenDescriptor.Issuer, issuedAt, conditions, advice, statements)
             {
                 SigningCredentials = tokenDescriptor.SigningCredentials
@@ -525,11 +538,11 @@ public virtual SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor
         }
 
         /// <summary>
-        /// Builds an XML formated string from a collection of saml attributes that represent an Actor. 
+        /// Builds an XML formated string from a collection of saml attributes that represent an Actor.
         /// </summary>
         /// <param name="attributes"><see cref="ICollection{SamlAttribute}"/>.</param>
         /// <returns>A well formed XML string.</returns>
-        /// <remarks>The string is of the form "&lt;Actor&gt;&lt;SamlAttribute name, ns&gt;&lt;SamlAttributeValue&gt;...&lt;/SamlAttributeValue&gt;, ...&lt;/SamlAttribute&gt;...&lt;/Actor&gt;"</remarks>        
+        /// <remarks>The string is of the form "&lt;Actor&gt;&lt;SamlAttribute name, ns&gt;&lt;SamlAttributeValue&gt;...&lt;/SamlAttributeValue&gt;, ...&lt;/SamlAttribute&gt;...&lt;/Actor&gt;"</remarks>
         protected virtual string CreateXmlStringFromAttributes(ICollection<SamlAttribute> attributes)
         {
             if (attributes == null)
@@ -795,7 +808,7 @@ public virtual SamlSecurityToken ReadSamlToken(XmlReader reader)
         /// <summary>
         /// Deserializes from XML a token of the type handled by this instance.
         /// </summary>
-        /// <param name="reader">An XML reader positioned at the token's start 
+        /// <param name="reader">An XML reader positioned at the token's start
         /// element.</param>
         /// <param name="validationParameters">The <see cref="TokenValidationParameters"/> to be used for validating the token.</param>
         /// <returns>An instance of <see cref="SamlSecurityToken"/>.</returns>
@@ -830,7 +843,7 @@ protected virtual SecurityKey ResolveIssuerSigningKey(string token, SamlSecurity
         }
 
         /// <summary>
-        /// This method gets called when a special type of SamlAttribute is detected. The SamlAttribute passed in wraps a SamlAttribute 
+        /// This method gets called when a special type of SamlAttribute is detected. The SamlAttribute passed in wraps a SamlAttribute
         /// that contains a collection of AttributeValues, each of which are mapped to a claim.  All of the claims will be returned
         /// in an ClaimsIdentity with the specified issuer.
         /// </summary>
@@ -1008,7 +1021,7 @@ protected virtual void ValidateLifetime(DateTime? notBefore, DateTime? expires,
         /// <exception cref="SecurityTokenValidationException">If <see cref="ReadSamlToken(string)"/> returns null"/>.</exception>
         /// <exception cref="SecurityTokenValidationException">If <see cref="TokenValidationParameters.SignatureValidator"/> returns null OR an object other than a <see cref="SamlSecurityToken"/>.</exception>
         /// <exception cref="SecurityTokenValidationException">If a signature is not found and <see cref="TokenValidationParameters.RequireSignedTokens"/> is true.</exception>
-        /// <exception cref="SecurityTokenSignatureKeyNotFoundException">If the 'token' has a key identifier and none of the <see cref="SecurityKey"/>(s) provided result in a validated signature. 
+        /// <exception cref="SecurityTokenSignatureKeyNotFoundException">If the 'token' has a key identifier and none of the <see cref="SecurityKey"/>(s) provided result in a validated signature.
         /// This can indicate that a key refresh is required.</exception>
         /// <exception cref="SecurityTokenInvalidSignatureException">If after trying all the <see cref="SecurityKey"/>(s), none result in a validated signture AND the 'token' does not have a key identifier.</exception>
         /// <returns>A <see cref="SamlSecurityToken"/> that has had the signature validated if token was signed.</returns>
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.cs
index 90184df46f..574993c049 100644
--- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.cs
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.cs
@@ -374,7 +374,7 @@ protected virtual void ValidateConfirmationData(Saml2SecurityToken samlToken, To
         /// <exception cref="SecurityTokenValidationException">If <see cref="ReadSaml2Token(string)"/> return null.</exception>
         /// <exception cref="SecurityTokenValidationException">If <see cref="TokenValidationParameters.SignatureValidator"/> returns null OR an object other than a <see cref="Saml2SecurityToken"/>.</exception>
         /// <exception cref="SecurityTokenValidationException">If a signature is not found and <see cref="TokenValidationParameters.RequireSignedTokens"/> is true.</exception>
-        /// <exception cref="SecurityTokenSignatureKeyNotFoundException">If the  <paramref name="token"/> has a key identifier and none of the <see cref="SecurityKey"/>(s) provided result in a validated signature. 
+        /// <exception cref="SecurityTokenSignatureKeyNotFoundException">If the  <paramref name="token"/> has a key identifier and none of the <see cref="SecurityKey"/>(s) provided result in a validated signature.
         /// This can indicate that a key refresh is required.</exception>
         /// <exception cref="SecurityTokenInvalidSignatureException">If after trying all the <see cref="SecurityKey"/>(s), none result in a validated signature AND the 'token' does not have a key identifier.</exception>
         /// <returns>A <see cref="Saml2SecurityToken"/> that has had the signature validated if token was signed.</returns>
@@ -608,9 +608,9 @@ internal static bool IsSaml2Assertion(XmlReader reader)
         /// </summary>
         /// <remarks>
         /// <para>
-        /// Generally, conditions should be included in assertions to limit the 
-        /// impact of misuse of the assertion. Specifying the NotBefore and 
-        /// NotOnOrAfter conditions can limit the period of vulnerability in 
+        /// Generally, conditions should be included in assertions to limit the
+        /// impact of misuse of the assertion. Specifying the NotBefore and
+        /// NotOnOrAfter conditions can limit the period of vulnerability in
         /// the case of a compromised assertion. The AudienceRestrictionCondition
         /// can be used to explicitly state the intended relying party or parties
         /// of the assertion, which coupled with appropriate audience restriction
@@ -619,8 +619,8 @@ internal static bool IsSaml2Assertion(XmlReader reader)
         /// </para>
         /// <para>
         /// The default implementation creates NotBefore and NotOnOrAfter conditions
-        /// based on the tokenDescriptor.Lifetime. It will also generate an 
-        /// AudienceRestrictionCondition limiting consumption of the assertion to 
+        /// based on the tokenDescriptor.Lifetime. It will also generate an
+        /// AudienceRestrictionCondition limiting consumption of the assertion to
         /// tokenDescriptor.Scope.Address.
         /// </para>
         /// </remarks>
@@ -636,12 +636,20 @@ protected virtual Saml2Conditions CreateConditions(SecurityTokenDescriptor token
             if (tokenDescriptor.NotBefore.HasValue)
                 conditions.NotBefore = tokenDescriptor.NotBefore.Value;
             else if (SetDefaultTimesOnTokenCreation)
-                conditions.NotBefore = DateTime.UtcNow;
+                conditions.NotBefore =
+#if SUPPORTS_TIME_PROVIDER
+                    tokenDescriptor.TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow;
 
             if (tokenDescriptor.Expires.HasValue)
                 conditions.NotOnOrAfter = tokenDescriptor.Expires.Value;
             else if (SetDefaultTimesOnTokenCreation)
-                conditions.NotOnOrAfter = DateTime.UtcNow + TimeSpan.FromMinutes(TokenLifetimeInMinutes);
+                conditions.NotOnOrAfter =
+#if SUPPORTS_TIME_PROVIDER
+                    tokenDescriptor.TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow + TimeSpan.FromMinutes(TokenLifetimeInMinutes);
 
             if (tokenDescriptor.Audiences.Count > 0)
             {
@@ -801,7 +809,7 @@ protected virtual ICollection<Saml2Attribute> ConsolidateAttributes(ICollection<
         /// <param name="actor">A <see cref="ClaimsIdentity"/> to be transformed.</param>
         /// <exception cref="ArgumentNullException">if <paramref name="actor"/> is null.</exception>
         /// <returns>A well-formed XML string.</returns>
-        /// <remarks>Normally this is called when creating a <see cref="Saml2Assertion"/> from a <see cref="ClaimsIdentity"/>. When <see cref="ClaimsIdentity.Actor"/> is not null, 
+        /// <remarks>Normally this is called when creating a <see cref="Saml2Assertion"/> from a <see cref="ClaimsIdentity"/>. When <see cref="ClaimsIdentity.Actor"/> is not null,
         /// this method is called to create an string representation to add as an attribute.
         /// <para>The string is formed: "&lt;Actor&gt;&lt;Attribute name, namespace&gt;&lt;AttributeValue&gt;...&lt;/AttributeValue&gt;, ...&lt;/Attribute&gt;...&lt;/Actor&gt;</para></remarks>
         protected string CreateActorString(ClaimsIdentity actor)
@@ -820,7 +828,7 @@ protected string CreateActorString(ClaimsIdentity actor)
         }
 
         /// <summary>
-        /// Builds an XML formatted string from a collection of SAML attributes that represent the Actor. 
+        /// Builds an XML formatted string from a collection of SAML attributes that represent the Actor.
         /// </summary>
         /// <param name="attributes">An enumeration of Saml2Attributes.</param>
         /// <returns>A well-formed XML string.</returns>
@@ -1059,14 +1067,14 @@ protected virtual void ValidateOneTimeUseCondition(Saml2SecurityToken securityTo
         }
 
         /// <summary>
-        /// This method gets called when a special type of Saml2Attribute is detected. The Saml2Attribute passed in 
-        /// wraps a Saml2Attribute that contains a collection of AttributeValues, each of which will get mapped to a 
+        /// This method gets called when a special type of Saml2Attribute is detected. The Saml2Attribute passed in
+        /// wraps a Saml2Attribute that contains a collection of AttributeValues, each of which will get mapped to a
         /// claim.  All of the claims will be returned in an ClaimsIdentity with the specified issuer.
         /// </summary>
         /// <param name="attribute">The <see cref="Saml2Attribute"/> to use.</param>
         /// <param name="identity">The <see cref="ClaimsIdentity"/> that is the subject of this token.</param>
         /// <param name="issuer">The issuer of the claim.</param>
-        /// <exception cref="InvalidOperationException">Will be thrown if the Saml2Attribute does not contain any 
+        /// <exception cref="InvalidOperationException">Will be thrown if the Saml2Attribute does not contain any
         /// valid Saml2AttributeValues.
         /// </exception>
         protected virtual void SetClaimsIdentityActorFromAttribute(Saml2Attribute attribute, ClaimsIdentity identity, string issuer)
diff --git a/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs b/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs
index 2124c416c4..ce74359eae 100644
--- a/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs
+++ b/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs
@@ -38,6 +38,18 @@ public TimeSpan AutomaticRefreshInterval
             }
         }
 
+#if SUPPORTS_TIME_PROVIDER
+#nullable enable
+        /// <summary>
+        /// Gets or sets the time provider.
+        /// </summary>
+        /// <remarks>
+        /// If not set, fall back to using the <see cref="DateTime"/> class to obtain the current time.
+        /// </remarks>
+        public TimeProvider? TimeProvider { get; set; }
+#nullable restore
+#endif
+
         /// <summary>
         /// Default time interval (12 hours) after which a new configuration is obtained automatically.
         /// </summary>
@@ -101,7 +113,13 @@ public virtual Task<BaseConfiguration> GetBaseConfigurationAsync(CancellationTok
         /// <returns>A collection of all valid last known good configurations.</returns>
         internal BaseConfiguration[] GetValidLkgConfigurations()
         {
-            return _lastKnownGoodConfigurationCache.ToArray().Where(x => x.Value.Value > DateTime.UtcNow).Select(x => x.Key).ToArray();
+            DateTime utcNow =
+#if SUPPORTS_TIME_PROVIDER
+                TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                DateTime.UtcNow;
+
+            return _lastKnownGoodConfigurationCache.ToArray().Where(x => x.Value.Value > utcNow).Select(x => x.Key).ToArray();
         }
 
         /// <summary>
@@ -116,10 +134,16 @@ public BaseConfiguration LastKnownGoodConfiguration
             set
             {
                 _lastKnownGoodConfiguration = value ?? throw LogHelper.LogArgumentNullException(nameof(value));
-                _lastKnownGoodConfigFirstUse = DateTime.UtcNow;
+                DateTime utcNow =
+#if SUPPORTS_TIME_PROVIDER
+                    TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow;
+
+                _lastKnownGoodConfigFirstUse = utcNow;
 
                 // LRU cache will remove the expired configuration
-                _lastKnownGoodConfigurationCache.SetValue(_lastKnownGoodConfiguration, DateTime.UtcNow + LastKnownGoodLifetime, DateTime.UtcNow + LastKnownGoodLifetime);
+                _lastKnownGoodConfigurationCache.SetValue(_lastKnownGoodConfiguration, utcNow + LastKnownGoodLifetime, DateTime.UtcNow + LastKnownGoodLifetime);
             }
         }
 
@@ -178,7 +202,21 @@ public TimeSpan RefreshInterval
         /// </summary>
         // The _lastKnownGoodConfiguration private variable is accessed rather than the property (LastKnownGoodConfiguration) as we do not want this access
         // to trigger a change in _lastKnownGoodConfigFirstUse.
-        public bool IsLastKnownGoodValid => _lastKnownGoodConfiguration != null && (_lastKnownGoodConfigFirstUse == null || DateTime.UtcNow < _lastKnownGoodConfigFirstUse + LastKnownGoodLifetime);
+        public bool IsLastKnownGoodValid
+        {
+            get
+            {
+                DateTime utcNow =
+#if SUPPORTS_TIME_PROVIDER
+                    TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow;
+
+                return _lastKnownGoodConfiguration != null && (_lastKnownGoodConfigFirstUse == null ||
+                                                               utcNow < _lastKnownGoodConfigFirstUse +
+                                                               LastKnownGoodLifetime);
+            }
+        }
 
         /// <summary>
         /// Indicate that the configuration may be stale (as indicated by failing to process incoming tokens).
diff --git a/src/Microsoft.IdentityModel.Tokens/SecurityTokenDescriptor.cs b/src/Microsoft.IdentityModel.Tokens/SecurityTokenDescriptor.cs
index b9b5843143..d5ae915dd1 100644
--- a/src/Microsoft.IdentityModel.Tokens/SecurityTokenDescriptor.cs
+++ b/src/Microsoft.IdentityModel.Tokens/SecurityTokenDescriptor.cs
@@ -77,7 +77,7 @@ public class SecurityTokenDescriptor
         /// Gets or sets the <see cref="Dictionary{TKey, TValue}"/> which contains any custom header claims that need to be added to the JWT token header.
         /// The 'alg', 'kid', 'x5t', 'enc', and 'zip' claims are added by default based on the <see cref="SigningCredentials"/>,
         /// <see cref="EncryptingCredentials"/>, and/or <see cref="CompressionAlgorithm"/> provided and SHOULD NOT be included in this dictionary as this
-        /// will result in an exception being thrown. 
+        /// will result in an exception being thrown.
         /// <remarks> These claims are only added to the outer header (in case of a JWE).</remarks>
         /// </summary>
         public IDictionary<string, object> AdditionalHeaderClaims { get; set; }
@@ -86,7 +86,7 @@ public class SecurityTokenDescriptor
         /// Gets or sets the <see cref="Dictionary{TKey, TValue}"/> which contains any custom header claims that need to be added to the inner JWT token header.
         /// The 'alg', 'kid', 'x5t', 'enc', and 'zip' claims are added by default based on the <see cref="SigningCredentials"/>,
         /// <see cref="EncryptingCredentials"/>, and/or <see cref="CompressionAlgorithm"/> provided and SHOULD NOT be included in this dictionary as this
-        /// will result in an exception being thrown. 
+        /// will result in an exception being thrown.
         /// <remarks>
         /// For JsonWebTokenHandler, these claims are merged with <see cref="AdditionalHeaderClaims"/> while adding to the inner JWT header.
         /// </remarks>
@@ -105,5 +105,17 @@ public class SecurityTokenDescriptor
         /// values will be overridden.
         /// </summary>
         public ClaimsIdentity Subject { get; set; }
+
+#if SUPPORTS_TIME_PROVIDER
+#nullable enable
+        /// <summary>
+        /// Gets or sets the time provider.
+        /// </summary>
+        /// <remarks>
+        /// If not set, fall back to using the <see cref="DateTime"/> class to obtain the current time.
+        /// </remarks>
+        public TimeProvider? TimeProvider { get; set; }
+#nullable restore
+#endif
     }
 }
diff --git a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
index 13059a7b76..443e514a25 100644
--- a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
+++ b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
@@ -544,13 +544,15 @@ public string RoleClaimType
         public SignatureValidatorUsingConfiguration SignatureValidatorUsingConfiguration { get; set; }
 
 #if SUPPORTS_TIME_PROVIDER
+#nullable enable
         /// <summary>
-        /// Gets or sets the time provider used for time validation.
+        /// Gets or sets the time provider.
         /// </summary>
         /// <remarks>
-        /// If not set, validators will fall back to using the <see cref="DateTime"/> class to obtain the current time.
+        /// If not set, fall back to using the <see cref="DateTime"/> class to obtain the current time.
         /// </remarks>
-        public TimeProvider TimeProvider { get; set; }
+        public TimeProvider? TimeProvider { get; set; }
+#nullable restore
 #endif
 
         /// <summary>
diff --git a/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs b/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs
index 9221780d0f..408bb6fca4 100644
--- a/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs
+++ b/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs
@@ -36,11 +36,12 @@ internal static void ValidateLifetime(DateTime? notBefore, DateTime? expires, Se
                     Expires = expires
                 });
 
+            DateTime utcNow =
 #if SUPPORTS_TIME_PROVIDER
-            DateTime utcNow = validationParameters.TimeProvider?.GetUtcNow().UtcDateTime ?? DateTime.UtcNow;
-#else
-            DateTime utcNow = DateTime.UtcNow;
+                validationParameters.TimeProvider?.GetUtcNow().UtcDateTime ??
 #endif
+                DateTime.UtcNow;
+
             if (notBefore.HasValue && (notBefore.Value > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew)))
                 throw LogHelper.LogExceptionMessage(new SecurityTokenNotYetValidException(LogHelper.FormatInvariant(LogMessages.IDX10222, LogHelper.MarkAsNonPII(notBefore.Value), LogHelper.MarkAsNonPII(utcNow)))
                 {
diff --git a/src/Microsoft.IdentityModel.Tokens/Validators.cs b/src/Microsoft.IdentityModel.Tokens/Validators.cs
index 6002eded77..ec5222e31a 100644
--- a/src/Microsoft.IdentityModel.Tokens/Validators.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Validators.cs
@@ -404,7 +404,12 @@ internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, T
             X509SecurityKey x509SecurityKey = securityKey as X509SecurityKey;
             if (x509SecurityKey?.Certificate is X509Certificate2 cert)
             {
-                DateTime utcNow = DateTime.UtcNow;
+                DateTime utcNow =
+#if SUPPORTS_TIME_PROVIDER
+                    validationParameters.TimeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow;
+
                 var notBeforeUtc = cert.NotBefore.ToUniversalTime();
                 var notAfterUtc = cert.NotAfter.ToUniversalTime();
 
diff --git a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs
index bad33a8ed9..84b50ce053 100644
--- a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs
+++ b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs
@@ -356,7 +356,11 @@ public virtual string CreateEncodedJwt(
                 expires,
                 issuedAt,
                 signingCredentials,
-                null, null, null, null, null).RawData;
+                null, null, null,
+#if SUPPORTS_TIME_PROVIDER
+                timeProvider: null,
+#endif
+                null, null).RawData;
         }
 
         /// <summary>
@@ -395,7 +399,11 @@ public virtual string CreateEncodedJwt(
                 expires,
                 issuedAt,
                 signingCredentials,
-                encryptingCredentials, null, null, null, null).RawData;
+                encryptingCredentials, null, null,
+#if SUPPORTS_TIME_PROVIDER
+                timeProvider: null,
+#endif
+                null, null).RawData;
         }
 
         /// <summary>
@@ -437,7 +445,11 @@ public virtual string CreateEncodedJwt(
                 issuedAt,
                 signingCredentials,
                 encryptingCredentials,
-                claimCollection, null, null, null).RawData;
+                claimCollection, null,
+#if SUPPORTS_TIME_PROVIDER
+                timeProvider: null,
+#endif
+                null, null).RawData;
         }
 
         /// <summary>
@@ -462,6 +474,9 @@ public virtual JwtSecurityToken CreateJwtSecurityToken(SecurityTokenDescriptor t
                 tokenDescriptor.EncryptingCredentials,
                 tokenDescriptor.Claims,
                 tokenDescriptor.TokenType,
+#if SUPPORTS_TIME_PROVIDER
+                tokenDescriptor.TimeProvider,
+#endif
                 tokenDescriptor.AdditionalHeaderClaims,
                 tokenDescriptor.AdditionalInnerHeaderClaims);
         }
@@ -505,7 +520,11 @@ public virtual JwtSecurityToken CreateJwtSecurityToken(
                 expires,
                 issuedAt,
                 signingCredentials,
-                encryptingCredentials, null, null, null, null);
+                encryptingCredentials, null, null,
+#if SUPPORTS_TIME_PROVIDER
+                timeProvider: null,
+#endif
+                null, null);
         }
 
         /// <summary>
@@ -550,7 +569,11 @@ public virtual JwtSecurityToken CreateJwtSecurityToken(
                 issuedAt,
                 signingCredentials,
                 encryptingCredentials,
-                claimCollection, null, null, null);
+                claimCollection, null,
+#if SUPPORTS_TIME_PROVIDER
+                timeProvider: null,
+#endif
+                null, null);
         }
 
         /// <summary>
@@ -588,7 +611,11 @@ public virtual JwtSecurityToken CreateJwtSecurityToken(
                 notBefore,
                 expires,
                 issuedAt,
-                signingCredentials, null, null, null, null, null);
+                signingCredentials, null, null, null,
+#if SUPPORTS_TIME_PROVIDER
+                timeProvider: null,
+#endif
+                null, null);
         }
 
         /// <summary>
@@ -613,6 +640,9 @@ public override SecurityToken CreateToken(SecurityTokenDescriptor tokenDescripto
                 tokenDescriptor.EncryptingCredentials,
                 tokenDescriptor.Claims,
                 tokenDescriptor.TokenType,
+#if SUPPORTS_TIME_PROVIDER
+                tokenDescriptor.TimeProvider,
+#endif
                 tokenDescriptor.AdditionalHeaderClaims,
                 tokenDescriptor.AdditionalInnerHeaderClaims);
         }
@@ -628,6 +658,11 @@ private JwtSecurityToken CreateJwtSecurityTokenPrivate(
             EncryptingCredentials encryptingCredentials,
             IDictionary<string, object> claimCollection,
             string tokenType,
+#if SUPPORTS_TIME_PROVIDER
+#nullable enable
+            TimeProvider? timeProvider,
+#nullable restore
+#endif
             IDictionary<string, object> additionalHeaderClaims,
             IDictionary<string, object> additionalInnerHeaderClaims)
         {
@@ -653,7 +688,11 @@ private JwtSecurityToken CreateJwtSecurityTokenPrivate(
         {
             if (SetDefaultTimesOnTokenCreation && (!expires.HasValue || !issuedAt.HasValue || !notBefore.HasValue))
             {
-                DateTime now = DateTime.UtcNow;
+                DateTime now =
+#if SUPPORTS_TIME_PROVIDER
+                    timeProvider?.GetUtcNow().UtcDateTime ??
+#endif
+                    DateTime.UtcNow;
                 if (!expires.HasValue)
                     expires = now + TimeSpan.FromMinutes(TokenLifetimeInMinutes);