diff --git a/build/common.props b/build/common.props
index 5428c3247d..b8c768afe9 100644
--- a/build/common.props
+++ b/build/common.props
@@ -32,6 +32,9 @@
true
+
+ $(DefineConstants);SUPPORTS_TIME_PROVIDER
+
false
@@ -72,5 +75,5 @@
true
-
+
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
+ ///
+ /// Gets or sets the time provider.
+ ///
+ ///
+ /// If not set, fall back to using the class to obtain the current time.
+ ///
+ public TimeProvider? TimeProvider { get; set; }
+#nullable restore
+#endif
+
///
/// Gets the algorithm mapping between OpenIdConnect and .Net for Hash algorithms.
/// a that contains mappings from the JWT namespace 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 claim should be created and added or not.
///
///
- /// will be used as a "cnf" claim value, if set.
+ /// will be used as a "cnf" claim value, if set.
/// Otherwise, a "cnf" claim value will be derived from .
///
public bool CreateCnf { get; set; } = true;
@@ -27,19 +27,19 @@ public class SignedHttpRequestCreationParameters
///
/// Gets or sets a value indicating whether the claim should be created and added or not.
///
- /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
+ /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
public bool CreateTs { get; set; } = true;
///
/// Gets or sets a value indicating whether the claim should be created and added or not.
///
- /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
+ /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
public bool CreateM { get; set; } = true;
///
/// Gets or sets a value indicating whether the claim should be created and added or not.
///
- /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
+ /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
public bool CreateU { get; set; } = true;
///
@@ -51,19 +51,19 @@ public class SignedHttpRequestCreationParameters
///
/// Gets or sets a value indicating whether the claim should be created and added or not.
///
- /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
+ /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
public bool CreateQ { get; set; }
///
/// Gets or sets a value indicating whether the claim should be created and added or not.
///
- /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
+ /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
public bool CreateH { get; set; }
///
/// Gets or sets a value indicating whether the claim should be created and added or not.
///
- /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
+ /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3
public bool CreateB { get; set; }
///
@@ -76,5 +76,17 @@ public class SignedHttpRequestCreationParameters
///
/// Allows for adjusting the local time so it matches a server time.
public TimeSpan TimeAdjustment { get; set; } = DefaultTimeAdjustment;
+
+#if SUPPORTS_TIME_PROVIDER
+#nullable enable
+ ///
+ /// Gets or sets the time provider.
+ ///
+ ///
+ /// If not set, fall back to using the class to obtain the current time.
+ ///
+ 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
{
///
- /// A handler designed for creating and validating signed http requests.
+ /// A handler designed for creating and validating signed http requests.
///
/// The handler implementation is based on 'A Method for Signing HTTP Requests for OAuth' specification.
public class SignedHttpRequestHandler
@@ -143,7 +143,7 @@ public string CreateSignedHttpRequest(SignedHttpRequestDescriptor signedHttpRequ
/// Creates a JSON representation of a HttpRequest payload.
///
/// A structure that wraps parameters needed for SignedHttpRequest creation.
- /// An opaque context used to store work and logs when working with authentication artifacts.
+ /// An opaque context used to store work and logs when working with authentication artifacts.
/// A JSON representation of an HttpRequest payload.
///
/// Users can utilize 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
/// A structure that wraps parameters needed for SignedHttpRequest creation.
///
/// This method will be executed only if is set to true.
- ///
+ ///
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)));
}
///
@@ -243,7 +249,7 @@ internal virtual void AddTsClaim(ref Utf8JsonWriter writer, SignedHttpRequestDes
/// A structure that wraps parameters needed for SignedHttpRequest creation.
///
/// This method will be executed only if is set to true.
- ///
+ ///
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
/// A structure that wraps parameters needed for SignedHttpRequest creation.
///
/// This method will be executed only if is set to true.
- ///
+ ///
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
/// A structure that wraps parameters needed for SignedHttpRequest creation.
///
/// This method will be executed only if is set to true.
- ///
+ ///
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
/// A structure that wraps parameters needed for SignedHttpRequest creation.
///
/// This method will be executed only if is set to true.
- ///
+ ///
internal void AddHClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor)
{
IDictionary sanitizedHeaders = SanitizeHeaders(signedHttpRequestDescriptor.HttpRequestData.Headers);
@@ -389,7 +395,7 @@ internal void AddHClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor s
/// A structure that wraps parameters needed for SignedHttpRequest creation.
///
/// This method will be executed only if is set to true.
- ///
+ ///
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
///
/// A structure that wraps parameters needed for SignedHttpRequest validation.
/// Propagates notification that operations should be canceled.
- /// A .
+ /// A .
/// will be true if the signed http request was successfully validated, false otherwise.
///
public async Task 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
}
///
- /// Validates the signed http request "u" claim.
+ /// Validates the signed http request "u" claim.
///
/// A SignedHttpRequest.
/// A structure that wraps parameters needed for SignedHttpRequest validation.
@@ -796,7 +807,7 @@ internal virtual void ValidateUClaim(JsonWebToken signedHttpRequest, SignedHttpR
}
///
- /// Validates the signed http request "p" claim.
+ /// Validates the signed http request "p" claim.
///
/// A SignedHttpRequest.
/// A structure that wraps parameters needed for SignedHttpRequest validation.
@@ -825,7 +836,7 @@ internal virtual void ValidatePClaim(JsonWebToken signedHttpRequest, SignedHttpR
}
///
- /// Validates the signed http request "q" claim.
+ /// Validates the signed http request "q" claim.
///
/// A SignedHttpRequest.
/// A structure that wraps parameters needed for SignedHttpRequest validation.
@@ -900,7 +911,7 @@ internal virtual void ValidateQClaim(JsonWebToken signedHttpRequest, SignedHttpR
}
///
- /// Validates the signed http request "h" claim.
+ /// Validates the signed http request "h" claim.
///
/// A SignedHttpRequest.
/// A structure that wraps parameters needed for SignedHttpRequest validation.
@@ -969,7 +980,7 @@ internal virtual void ValidateHClaim(JsonWebToken signedHttpRequest, SignedHttpR
}
///
- /// Validates the signed http request "b" claim.
+ /// Validates the signed http request "b" claim.
///
/// A SignedHttpRequest.
/// A structure that wraps parameters needed for SignedHttpRequest validation.
@@ -1082,7 +1093,7 @@ internal virtual async Task ResolvePopKeyFromCnfClaimAsync(Cnf cnf,
}
///
- /// Resolves a PoP from the asymmetric representation of a PoP key.
+ /// Resolves a PoP from the asymmetric representation of a PoP key.
///
/// The JsonWebKey to resolve.
/// A structure that wraps parameters needed for SignedHttpRequest validation.
@@ -1103,7 +1114,7 @@ internal virtual SecurityKey ResolvePopKeyFromJwk(JsonWebKey jsonWebKey, SignedH
}
///
- /// Resolves a PoP from the encrypted symmetric representation of a PoP key.
+ /// Resolves a PoP from the encrypted symmetric representation of a PoP key.
///
/// An encrypted symmetric representation of a PoP key (JSON).
/// A structure that wraps parameters needed for SignedHttpRequest validation.
@@ -1119,7 +1130,7 @@ internal virtual async Task ResolvePopKeyFromJweAsync(string jwe, S
}
///
- /// Resolves a PoP from the URL reference to a PoP key.
+ /// Resolves a PoP from the URL reference to a PoP key.
///
/// A URL reference to a PoP JWK set.
/// A confirmation ("cnf") claim as a JObject.
@@ -1203,7 +1214,7 @@ internal virtual async Task> GetPopKeysFromJkuAsync(string jk
}
///
- /// Resolves a PoP using a key identifier of a PoP key.
+ /// Resolves a PoP using a key identifier of a PoP key.
///
/// A claim value.
/// A signed http request as a JWT.
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 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 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 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 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 SamlSubjectEqualityComparer
///
/// Gets or set the that will be used to read and write a .
///
- /// 'value' is null.
+ /// 'value' is null.
public SamlSerializer Serializer
{
get { return _serializer; }
@@ -186,7 +186,7 @@ protected virtual ICollection ConsolidateAttributes(ICollection
- /// Override this method to provide a SamlAdvice to place in the Samltoken.
+ /// Override this method to provide a SamlAdvice to place in the Samltoken.
///
/// Contains information about the token.
/// SamlAdvice, default is null.
@@ -286,7 +286,7 @@ protected virtual SamlAttributeStatement CreateAttributeStatement(SamlSubject su
}
///
- /// 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.
///
/// The SamlSubject of the Statement.
@@ -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
}
///
- /// 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.
///
/// .
/// A well formed XML string.
- /// The string is of the form "<Actor><SamlAttribute name, ns><SamlAttributeValue>...</SamlAttributeValue>, ...</SamlAttribute>...</Actor>"
+ /// The string is of the form "<Actor><SamlAttribute name, ns><SamlAttributeValue>...</SamlAttributeValue>, ...</SamlAttribute>...</Actor>"
protected virtual string CreateXmlStringFromAttributes(ICollection attributes)
{
if (attributes == null)
@@ -795,7 +808,7 @@ public virtual SamlSecurityToken ReadSamlToken(XmlReader reader)
///
/// Deserializes from XML a token of the type handled by this instance.
///
- /// An XML reader positioned at the token's start
+ /// An XML reader positioned at the token's start
/// element.
/// The to be used for validating the token.
/// An instance of .
@@ -830,7 +843,7 @@ protected virtual SecurityKey ResolveIssuerSigningKey(string token, SamlSecurity
}
///
- /// 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.
///
@@ -1008,7 +1021,7 @@ protected virtual void ValidateLifetime(DateTime? notBefore, DateTime? expires,
/// If returns null"/>.
/// If returns null OR an object other than a .
/// If a signature is not found and is true.
- /// If the 'token' has a key identifier and none of the (s) provided result in a validated signature.
+ /// If the 'token' has a key identifier and none of the (s) provided result in a validated signature.
/// This can indicate that a key refresh is required.
/// If after trying all the (s), none result in a validated signture AND the 'token' does not have a key identifier.
/// A that has had the signature validated if token was signed.
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
/// If return null.
/// If returns null OR an object other than a .
/// If a signature is not found and is true.
- /// If the has a key identifier and none of the (s) provided result in a validated signature.
+ /// If the has a key identifier and none of the (s) provided result in a validated signature.
/// This can indicate that a key refresh is required.
/// If after trying all the (s), none result in a validated signature AND the 'token' does not have a key identifier.
/// A that has had the signature validated if token was signed.
@@ -608,9 +608,9 @@ internal static bool IsSaml2Assertion(XmlReader reader)
///
///
///
- /// 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)
///
///
/// 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.
///
///
@@ -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 ConsolidateAttributes(ICollection<
/// A to be transformed.
/// if is null.
/// A well-formed XML string.
- /// Normally this is called when creating a from a . When is not null,
+ /// Normally this is called when creating a from a . When is not null,
/// this method is called to create an string representation to add as an attribute.
/// The string is formed: "<Actor><Attribute name, namespace><AttributeValue>...</AttributeValue>, ...</Attribute>...</Actor>
protected string CreateActorString(ClaimsIdentity actor)
@@ -820,7 +828,7 @@ protected string CreateActorString(ClaimsIdentity actor)
}
///
- /// 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.
///
/// An enumeration of Saml2Attributes.
/// A well-formed XML string.
@@ -1059,14 +1067,14 @@ protected virtual void ValidateOneTimeUseCondition(Saml2SecurityToken securityTo
}
///
- /// 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.
///
/// The to use.
/// The that is the subject of this token.
/// The issuer of the claim.
- /// Will be thrown if the Saml2Attribute does not contain any
+ /// Will be thrown if the Saml2Attribute does not contain any
/// valid Saml2AttributeValues.
///
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
+ ///
+ /// Gets or sets the time provider.
+ ///
+ ///
+ /// If not set, fall back to using the class to obtain the current time.
+ ///
+ public TimeProvider? TimeProvider { get; set; }
+#nullable restore
+#endif
+
///
/// Default time interval (12 hours) after which a new configuration is obtained automatically.
///
@@ -101,7 +113,13 @@ public virtual Task GetBaseConfigurationAsync(CancellationTok
/// A collection of all valid last known good configurations.
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();
}
///
@@ -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
///
// 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);
+ }
+ }
///
/// 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 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 ,
/// , and/or 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.
/// These claims are only added to the outer header (in case of a JWE).
///
public IDictionary AdditionalHeaderClaims { get; set; }
@@ -86,7 +86,7 @@ public class SecurityTokenDescriptor
/// Gets or sets the 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 ,
/// , and/or 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.
///
/// For JsonWebTokenHandler, these claims are merged with while adding to the inner JWT header.
///
@@ -105,5 +105,17 @@ public class SecurityTokenDescriptor
/// values will be overridden.
///
public ClaimsIdentity Subject { get; set; }
+
+#if SUPPORTS_TIME_PROVIDER
+#nullable enable
+ ///
+ /// Gets or sets the time provider.
+ ///
+ ///
+ /// If not set, fall back to using the class to obtain the current time.
+ ///
+ 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 ab93e8f58a..443e514a25 100644
--- a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
+++ b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
@@ -82,6 +82,9 @@ protected TokenValidationParameters(TokenValidationParameters other)
SaveSigninToken = other.SaveSigninToken;
SignatureValidator = other.SignatureValidator;
SignatureValidatorUsingConfiguration = other.SignatureValidatorUsingConfiguration;
+#if SUPPORTS_TIME_PROVIDER
+ TimeProvider = other.TimeProvider;
+#endif
TokenDecryptionKey = other.TokenDecryptionKey;
TokenDecryptionKeyResolver = other.TokenDecryptionKeyResolver;
TokenDecryptionKeys = other.TokenDecryptionKeys;
@@ -352,7 +355,7 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken,
/// This means that no default 'issuer' validation will occur.
/// Even if is false, this delegate will still be called.
/// If both and are set, IssuerValidatorUsingConfiguration takes
- /// priority.
+ /// priority.
///
public IssuerValidator IssuerValidator { get; set; }
@@ -540,6 +543,18 @@ public string RoleClaimType
///
public SignatureValidatorUsingConfiguration SignatureValidatorUsingConfiguration { get; set; }
+#if SUPPORTS_TIME_PROVIDER
+#nullable enable
+ ///
+ /// Gets or sets the time provider.
+ ///
+ ///
+ /// If not set, fall back to using the class to obtain the current time.
+ ///
+ public TimeProvider? TimeProvider { get; set; }
+#nullable restore
+#endif
+
///
/// Gets or sets the that is to be used for decryption.
///
@@ -649,7 +664,7 @@ public string RoleClaimType
/// Gets or sets a boolean that controls if validation of the that signed the securityToken is called.
///
/// It is possible for tokens to contain the public key needed to check the signature. For example, X509Data can be hydrated into an X509Certificate,
- /// which can be used to validate the signature. In these cases it is important to validate the SigningKey that was used to validate the signature.
+ /// which can be used to validate the signature. In these cases it is important to validate the SigningKey that was used to validate the signature.
/// This boolean only applies to default signing key validation. If is set, it will be called regardless of whether this
/// property is true or false.
/// The default is false.
@@ -679,7 +694,7 @@ public string RoleClaimType
///
/// Gets or sets a boolean to control if the token replay will be validated during token validation.
- ///
+ ///
///
/// This boolean only applies to default token replay validation. If is set, it will be called regardless of whether this
/// property is true or false.
diff --git a/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs b/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs
index 6945912645..408bb6fca4 100644
--- a/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs
+++ b/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs
@@ -36,7 +36,12 @@ internal static void ValidateLifetime(DateTime? notBefore, DateTime? expires, Se
Expires = expires
});
- DateTime utcNow = DateTime.UtcNow;
+ DateTime utcNow =
+#if SUPPORTS_TIME_PROVIDER
+ 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;
}
///
@@ -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;
}
///
@@ -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;
}
///
@@ -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);
}
///
@@ -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);
}
///
@@ -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);
}
///
@@ -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 claimCollection,
string tokenType,
+#if SUPPORTS_TIME_PROVIDER
+#nullable enable
+ TimeProvider? timeProvider,
+#nullable restore
+#endif
IDictionary additionalHeaderClaims,
IDictionary 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);