diff --git a/Wilson.sln b/Wilson.sln
index 9c671e6168..8dd5dcbce2 100644
--- a/Wilson.sln
+++ b/Wilson.sln
@@ -85,6 +85,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4655DBB4-70C6-475D-8971-FE6619B85F70}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
+ buildPack.bat = buildPack.bat
+ buildTestPack.bat = buildTestPack.bat
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.Validators", "src\Microsoft.IdentityModel.Validators\Microsoft.IdentityModel.Validators.csproj", "{DA585910-0E6C-45A5-AABD-30917130FD63}"
diff --git a/buildPack.bat b/buildPack.bat
index 7e782b647f..6a4c89623c 100644
--- a/buildPack.bat
+++ b/buildPack.bat
@@ -1,2 +1,3 @@
+dotnet clean Product.proj > clean.log
dotnet build /r Product.proj
dotnet pack --no-restore -o artifacts --no-build Product.proj
diff --git a/buildTestPack.bat b/buildTestPack.bat
index 40e59fda52..b5dba1b86f 100644
--- a/buildTestPack.bat
+++ b/buildTestPack.bat
@@ -1,3 +1,4 @@
+dotnet clean Product.proj > clean.log
dotnet build /r Product.proj
dotnet test --no-restore --no-build Product.proj
dotnet pack --no-restore -o artifacts --no-build Product.proj
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs
new file mode 100644
index 0000000000..3242311e9a
--- /dev/null
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs
@@ -0,0 +1,771 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.IdentityModel.Abstractions;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Tokens;
+using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
+
+namespace Microsoft.IdentityModel.JsonWebTokens
+{
+ ///
+ /// A designed for creating and validating Json Web Tokens.
+ /// See: https://datatracker.ietf.org/doc/html/rfc7519 and http://www.rfc-editor.org/info/rfc7515.
+ ///
+ public partial class JsonWebTokenHandler : TokenHandler
+ {
+ ///
+ /// Returns a value that indicates if this handler can validate a .
+ ///
+ /// 'true', indicating this instance can validate a .
+ public virtual bool CanValidateToken
+ {
+ get { return true; }
+ }
+
+ internal async ValueTask ValidateJWEAsync(
+ JsonWebToken jwtToken,
+ TokenValidationParameters validationParameters,
+ BaseConfiguration configuration)
+ {
+ try
+ {
+ TokenValidationResult tokenValidationResult = ReadToken(DecryptToken(jwtToken, validationParameters), validationParameters);
+ if (!tokenValidationResult.IsValid)
+ return tokenValidationResult;
+
+
+ tokenValidationResult = await ValidateJWSAsync(
+ tokenValidationResult.SecurityToken as JsonWebToken,
+ validationParameters,
+ configuration).ConfigureAwait(false);
+
+ if (!tokenValidationResult.IsValid)
+ return tokenValidationResult;
+
+ jwtToken.InnerToken = tokenValidationResult.SecurityToken as JsonWebToken;
+ jwtToken.Payload = (tokenValidationResult.SecurityToken as JsonWebToken).Payload;
+ return new TokenValidationResult
+ {
+ SecurityToken = jwtToken,
+ ClaimsIdentityNoLocking = tokenValidationResult.ClaimsIdentityNoLocking,
+ IsValid = true,
+ TokenType = tokenValidationResult.TokenType
+ };
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ return new TokenValidationResult
+ {
+ Exception = ex,
+ IsValid = false,
+ TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jwtToken : null
+ };
+ }
+ }
+
+ internal async ValueTask ValidateJWEAsync(
+ JsonWebToken jwtToken,
+ TokenValidationParameters validationParameters,
+ CallContext callContext,
+ CancellationToken cancellationToken)
+ {
+ try
+ {
+ TokenValidationResult tokenValidationResult = ReadToken(DecryptToken(jwtToken, validationParameters), validationParameters);
+ if (!tokenValidationResult.IsValid)
+ return tokenValidationResult;
+
+ tokenValidationResult = await ValidateJWSAsync(
+ tokenValidationResult.SecurityToken as JsonWebToken,
+ validationParameters,
+ callContext,
+ cancellationToken).ConfigureAwait(false);
+
+ if (!tokenValidationResult.IsValid)
+ return tokenValidationResult;
+
+ jwtToken.InnerToken = tokenValidationResult.SecurityToken as JsonWebToken;
+ jwtToken.Payload = (tokenValidationResult.SecurityToken as JsonWebToken).Payload;
+ return new TokenValidationResult
+ {
+ SecurityToken = jwtToken,
+ ClaimsIdentityNoLocking = tokenValidationResult.ClaimsIdentityNoLocking,
+ IsValid = true,
+ TokenType = tokenValidationResult.TokenType
+ };
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ return new TokenValidationResult
+ {
+ Exception = ex,
+ IsValid = false,
+ TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jwtToken : null
+ };
+ }
+ }
+
+ internal async ValueTask ValidateJWSAsync(
+ JsonWebToken jsonWebToken,
+ TokenValidationParameters validationParameters,
+ BaseConfiguration configuration)
+ {
+ try
+ {
+ TokenValidationResult tokenValidationResult;
+ if (validationParameters.TransformBeforeSignatureValidation != null)
+ jsonWebToken = validationParameters.TransformBeforeSignatureValidation(jsonWebToken, validationParameters) as JsonWebToken;
+
+ if (validationParameters.SignatureValidator != null || validationParameters.SignatureValidatorUsingConfiguration != null)
+ {
+ var validatedToken = ValidateSignatureUsingDelegates(jsonWebToken, validationParameters);
+ tokenValidationResult = await ValidateTokenPayloadAsync(
+ validatedToken,
+ validationParameters,
+ configuration).ConfigureAwait(false);
+
+ Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, validatedToken, validationParameters);
+ }
+ else
+ {
+ if (validationParameters.ValidateSignatureLast)
+ {
+ tokenValidationResult = await ValidateTokenPayloadAsync(
+ jsonWebToken,
+ validationParameters,
+ configuration).ConfigureAwait(false);
+
+ if (tokenValidationResult.IsValid)
+ tokenValidationResult.SecurityToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, configuration);
+ }
+ else
+ {
+ var validatedToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, configuration);
+ tokenValidationResult = await ValidateTokenPayloadAsync(
+ validatedToken,
+ validationParameters,
+ configuration).ConfigureAwait(false);
+ }
+ }
+
+ return tokenValidationResult;
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ return new TokenValidationResult
+ {
+ Exception = ex,
+ IsValid = false,
+ TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jsonWebToken : null
+ };
+ }
+ }
+
+ internal async ValueTask ValidateJWSAsync(
+ JsonWebToken jsonWebToken,
+ TokenValidationParameters validationParameters,
+ CallContext callContext,
+ CancellationToken cancellationToken)
+ {
+ try
+ {
+ BaseConfiguration currentConfiguration = null;
+ if (validationParameters.ConfigurationManager != null)
+ {
+ try
+ {
+ currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ // The exception is not re-thrown as the TokenValidationParameters may have the issuer and signing key set
+ // directly on them, allowing the library to continue with token validation.
+ if (LogHelper.IsEnabled(EventLogLevel.Warning))
+ LogHelper.LogWarning(LogHelper.FormatInvariant(TokenLogMessages.IDX10261, validationParameters.ConfigurationManager.MetadataAddress, ex.ToString()));
+ }
+ }
+
+ TokenValidationResult tokenValidationResult;
+ if (validationParameters.TransformBeforeSignatureValidation != null)
+ jsonWebToken = validationParameters.TransformBeforeSignatureValidation(jsonWebToken, validationParameters) as JsonWebToken;
+
+ if (validationParameters.SignatureValidator != null || validationParameters.SignatureValidatorUsingConfiguration != null)
+ {
+ var validatedToken = ValidateSignatureUsingDelegates(jsonWebToken, validationParameters);
+ tokenValidationResult = await ValidateTokenPayloadAsync(
+ validatedToken,
+ validationParameters,
+ callContext,
+ cancellationToken).ConfigureAwait(false);
+
+ Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, validatedToken, validationParameters);
+ }
+ else
+ {
+ if (validationParameters.ValidateSignatureLast)
+ {
+ tokenValidationResult = await ValidateTokenPayloadAsync(
+ jsonWebToken,
+ validationParameters,
+ callContext,
+ cancellationToken).ConfigureAwait(false);
+
+ if (tokenValidationResult.IsValid)
+ tokenValidationResult.SecurityToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, currentConfiguration);
+ }
+ else
+ {
+ var validatedToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, currentConfiguration);
+ tokenValidationResult = await ValidateTokenPayloadAsync(
+ validatedToken,
+ validationParameters,
+ callContext,
+ cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ return tokenValidationResult;
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ return new TokenValidationResult
+ {
+ Exception = ex,
+ IsValid = false,
+ TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jsonWebToken : null
+ };
+ }
+ }
+
+ private static JsonWebToken ValidateSignatureAndIssuerSecurityKey(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
+ {
+ JsonWebToken validatedToken = ValidateSignature(jsonWebToken, validationParameters, configuration);
+ Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, jsonWebToken, validationParameters, configuration);
+ return validatedToken;
+ }
+
+ ///
+ /// Validates the JWT signature.
+ ///
+ private static JsonWebToken ValidateSignature(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
+ {
+ bool kidMatched = false;
+ IEnumerable keys = null;
+
+ if (!jwtToken.IsSigned)
+ {
+ if (validationParameters.RequireSignedTokens)
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10504, jwtToken)));
+ else
+ return jwtToken;
+ }
+
+ if (validationParameters.IssuerSigningKeyResolverUsingConfiguration != null)
+ {
+ keys = validationParameters.IssuerSigningKeyResolverUsingConfiguration(jwtToken.EncodedToken, jwtToken, jwtToken.Kid, validationParameters, configuration);
+ }
+ else if (validationParameters.IssuerSigningKeyResolver != null)
+ {
+ keys = validationParameters.IssuerSigningKeyResolver(jwtToken.EncodedToken, jwtToken, jwtToken.Kid, validationParameters);
+ }
+ else
+ {
+ var key = JwtTokenUtilities.ResolveTokenSigningKey(jwtToken.Kid, jwtToken.X5t, validationParameters, configuration);
+ if (key != null)
+ {
+ kidMatched = true;
+ keys = [key];
+ }
+ }
+
+ if (validationParameters.TryAllIssuerSigningKeys && keys.IsNullOrEmpty())
+ {
+ // control gets here if:
+ // 1. User specified delegate: IssuerSigningKeyResolver returned null
+ // 2. ResolveIssuerSigningKey returned null
+ // Try all the keys. This is the degenerate case, not concerned about perf.
+ keys = TokenUtilities.GetAllSigningKeys(configuration, validationParameters);
+ }
+
+ // keep track of exceptions thrown, keys that were tried
+ StringBuilder exceptionStrings = null;
+ StringBuilder keysAttempted = null;
+ var kidExists = !string.IsNullOrEmpty(jwtToken.Kid);
+
+ if (keys != null)
+ {
+ foreach (var key in keys)
+ {
+#pragma warning disable CA1031 // Do not catch general exception types
+ try
+ {
+ if (ValidateSignature(jwtToken, key, validationParameters))
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(TokenLogMessages.IDX10242, jwtToken);
+
+ jwtToken.SigningKey = key;
+ return jwtToken;
+ }
+ }
+ catch (Exception ex)
+ {
+ (exceptionStrings ??= new StringBuilder()).AppendLine(ex.ToString());
+ }
+#pragma warning restore CA1031 // Do not catch general exception types
+
+ if (key != null)
+ {
+ (keysAttempted ??= new StringBuilder()).Append(key.ToString()).Append(" , KeyId: ").AppendLine(key.KeyId);
+ if (kidExists && !kidMatched && key.KeyId != null)
+ kidMatched = jwtToken.Kid.Equals(key.KeyId, key is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+ }
+ }
+ }
+
+ // Get information on where keys used during token validation came from for debugging purposes.
+ var keysInTokenValidationParameters = TokenUtilities.GetAllSigningKeys(validationParameters: validationParameters);
+
+ var keysInConfiguration = TokenUtilities.GetAllSigningKeys(configuration);
+ var numKeysInTokenValidationParameters = keysInTokenValidationParameters.Count();
+ var numKeysInConfiguration = keysInConfiguration.Count();
+
+ if (kidExists)
+ {
+ if (kidMatched)
+ {
+ JsonWebToken localJwtToken = jwtToken; // avoid closure on non-exceptional path
+ var isKidInTVP = keysInTokenValidationParameters.Any(x => x.KeyId.Equals(localJwtToken.Kid));
+ var keyLocation = isKidInTVP ? "TokenValidationParameters" : "Configuration";
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10511,
+ LogHelper.MarkAsNonPII((object)keysAttempted ?? ""),
+ LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters),
+ LogHelper.MarkAsNonPII(numKeysInConfiguration),
+ LogHelper.MarkAsNonPII(keyLocation),
+ LogHelper.MarkAsNonPII(jwtToken.Kid),
+ (object)exceptionStrings ?? "",
+ jwtToken)));
+ }
+
+ if (!validationParameters.ValidateSignatureLast)
+ {
+ InternalValidators.ValidateAfterSignatureFailed(
+ jwtToken,
+ jwtToken.ValidFromNullable,
+ jwtToken.ValidToNullable,
+ jwtToken.Audiences,
+ validationParameters,
+ configuration);
+ }
+ }
+
+ if (keysAttempted is not null)
+ {
+ if (kidExists)
+ {
+ throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10503,
+ LogHelper.MarkAsNonPII(jwtToken.Kid),
+ LogHelper.MarkAsNonPII((object)keysAttempted ?? ""),
+ LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters),
+ LogHelper.MarkAsNonPII(numKeysInConfiguration),
+ (object)exceptionStrings ?? "",
+ jwtToken)));
+ }
+ else
+ {
+ throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10517,
+ LogHelper.MarkAsNonPII((object)keysAttempted ?? ""),
+ LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters),
+ LogHelper.MarkAsNonPII(numKeysInConfiguration),
+ (object)exceptionStrings ?? "",
+ jwtToken)));
+ }
+ }
+
+ throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(TokenLogMessages.IDX10500));
+ }
+
+ internal static bool IsSignatureValid(byte[] signatureBytes, int signatureBytesLength, SignatureProvider signatureProvider, byte[] dataToVerify, int dataToVerifyLength)
+ {
+ if (signatureProvider is SymmetricSignatureProvider)
+ {
+ return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, signatureBytes, 0, signatureBytesLength);
+ }
+ else
+ {
+ if (signatureBytes.Length == signatureBytesLength)
+ {
+ return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, signatureBytes, 0, signatureBytesLength);
+ }
+ else
+ {
+ byte[] sigBytes = new byte[signatureBytesLength];
+ Array.Copy(signatureBytes, 0, sigBytes, 0, signatureBytesLength);
+ return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, sigBytes, 0, signatureBytesLength);
+ }
+ }
+ }
+
+ internal static bool ValidateSignature(byte[] bytes, int len, string stringWithSignature, int signatureStartIndex, SignatureProvider signatureProvider)
+ {
+ return Base64UrlEncoding.Decode(
+ stringWithSignature,
+ signatureStartIndex + 1,
+ stringWithSignature.Length - signatureStartIndex - 1,
+ signatureProvider,
+ bytes,
+ len,
+ IsSignatureValid);
+ }
+
+ internal static bool ValidateSignature(JsonWebToken jsonWebToken, SecurityKey key, TokenValidationParameters validationParameters)
+ {
+ var cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory;
+ if (!cryptoProviderFactory.IsSupportedAlgorithm(jsonWebToken.Alg, key))
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX14000, LogHelper.MarkAsNonPII(jsonWebToken.Alg), key);
+
+ return false;
+ }
+
+ Validators.ValidateAlgorithm(jsonWebToken.Alg, key, jsonWebToken, validationParameters);
+ var signatureProvider = cryptoProviderFactory.CreateForVerifying(key, jsonWebToken.Alg);
+ try
+ {
+ if (signatureProvider == null)
+ throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(TokenLogMessages.IDX10636, key == null ? "Null" : key.ToString(), LogHelper.MarkAsNonPII(jsonWebToken.Alg))));
+
+ return EncodingUtils.PerformEncodingDependentOperation(
+ jsonWebToken.EncodedToken,
+ 0,
+ jsonWebToken.Dot2,
+ Encoding.UTF8,
+ jsonWebToken.EncodedToken,
+ jsonWebToken.Dot2,
+ signatureProvider,
+ ValidateSignature);
+ }
+ finally
+ {
+ cryptoProviderFactory.ReleaseSignatureProvider(signatureProvider);
+ }
+ }
+
+ private static JsonWebToken ValidateSignatureUsingDelegates(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters)
+ {
+ if (validationParameters.SignatureValidatorUsingConfiguration != null)
+ {
+ // TODO - get configuration from validationParameters
+ BaseConfiguration configuration = null;
+ var validatedToken = validationParameters.SignatureValidatorUsingConfiguration(jsonWebToken.EncodedToken, validationParameters, configuration);
+ if (validatedToken == null)
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken)));
+
+ if (!(validatedToken is JsonWebToken validatedJsonWebToken))
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10506, LogHelper.MarkAsNonPII(typeof(JsonWebToken)), LogHelper.MarkAsNonPII(validatedToken.GetType()), jsonWebToken)));
+
+ return validatedJsonWebToken;
+ }
+ else if (validationParameters.SignatureValidator != null)
+ {
+ var validatedToken = validationParameters.SignatureValidator(jsonWebToken.EncodedToken, validationParameters);
+ if (validatedToken == null)
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken)));
+
+ if (!(validatedToken is JsonWebToken validatedJsonWebToken))
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10506, LogHelper.MarkAsNonPII(typeof(JsonWebToken)), LogHelper.MarkAsNonPII(validatedToken.GetType()), jsonWebToken)));
+
+ return validatedJsonWebToken;
+ }
+
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken)));
+ }
+
+ ///
+ /// Validates a JWS or a JWE.
+ ///
+ /// A 'JSON Web Token' (JWT) in JWS or JWE Compact Serialization Format.
+ /// A required for validation.
+ /// A
+ [Obsolete("`JsonWebTokens.ValidateToken(string, TokenValidationParameters)` has been deprecated and will be removed in a future release. Use `JsonWebTokens.ValidateTokenAsync(string, TokenValidationParameters)` instead. For more information, see https://aka.ms/IdentityModel/7-breaking-changes", false)]
+ public virtual TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters)
+ {
+ return ValidateTokenAsync(token, validationParameters).ConfigureAwait(false).GetAwaiter().GetResult();
+ }
+
+ ///
+ /// Validates a token.
+ /// On a validation failure, no exception will be thrown; instead, the exception will be set in the returned TokenValidationResult.Exception property.
+ /// Callers should always check the TokenValidationResult.IsValid property to verify the validity of the result.
+ ///
+ /// The token to be validated.
+ /// A required for validation.
+ /// A
+ ///
+ /// TokenValidationResult.Exception will be set to one of the following exceptions if the is invalid.
+ /// if is null or empty.
+ /// if is null.
+ /// 'token.Length' is greater than .
+ /// if is not a valid ,
+ /// if the validationParameters.TokenReader delegate is not able to parse/read the token as a valid ,
+ ///
+ public override async Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters)
+ {
+ if (string.IsNullOrEmpty(token))
+ return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(token)), IsValid = false };
+
+ if (validationParameters == null)
+ return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(validationParameters)), IsValid = false };
+
+ if (token.Length > MaximumTokenSizeInBytes)
+ return new TokenValidationResult { Exception = LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(TokenLogMessages.IDX10209, LogHelper.MarkAsNonPII(token.Length), LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)))), IsValid = false };
+
+ try
+ {
+ TokenValidationResult result = ReadToken(token, validationParameters);
+ if (result.IsValid)
+ return await ValidateTokenAsync(result.SecurityToken, validationParameters).ConfigureAwait(false);
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ return new TokenValidationResult
+ {
+ Exception = ex,
+ IsValid = false
+ };
+ }
+ }
+
+ ///
+ public override async Task ValidateTokenAsync(SecurityToken token, TokenValidationParameters validationParameters)
+ {
+ if (token == null)
+ throw LogHelper.LogArgumentNullException(nameof(token));
+
+ if (validationParameters == null)
+ return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(validationParameters)), IsValid = false };
+
+ var jwt = token as JsonWebToken;
+ if (jwt == null)
+ return new TokenValidationResult { Exception = LogHelper.LogArgumentException(nameof(token), $"{nameof(token)} must be a {nameof(JsonWebToken)}."), IsValid = false };
+
+ try
+ {
+ return await ValidateTokenAsync(jwt, validationParameters).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ return new TokenValidationResult
+ {
+ Exception = ex,
+ IsValid = false
+ };
+ }
+ }
+
+ ///
+ /// Internal method for token validation, responsible for:
+ /// (1) Obtaining a configuration from the .
+ /// (2) Revalidating using the Last Known Good Configuration (if present), and obtaining a refreshed configuration (if necessary) and revalidating using it.
+ ///
+ /// The JWT token
+ /// The to be used for validation.
+ ///
+ internal async ValueTask ValidateTokenAsync(
+ JsonWebToken jsonWebToken,
+ TokenValidationParameters validationParameters)
+ {
+ BaseConfiguration currentConfiguration = null;
+ if (validationParameters.ConfigurationManager != null)
+ {
+ try
+ {
+ currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ // The exception is not re-thrown as the TokenValidationParameters may have the issuer and signing key set
+ // directly on them, allowing the library to continue with token validation.
+ if (LogHelper.IsEnabled(EventLogLevel.Warning))
+ LogHelper.LogWarning(LogHelper.FormatInvariant(TokenLogMessages.IDX10261, validationParameters.ConfigurationManager.MetadataAddress, ex.ToString()));
+ }
+ }
+
+ TokenValidationResult tokenValidationResult = jsonWebToken.IsEncrypted ?
+ await ValidateJWEAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false) :
+ await ValidateJWSAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false);
+
+ if (validationParameters.ConfigurationManager != null)
+ {
+ if (tokenValidationResult.IsValid)
+ {
+ // Set current configuration as LKG if it exists.
+ if (currentConfiguration != null)
+ validationParameters.ConfigurationManager.LastKnownGoodConfiguration = currentConfiguration;
+
+ return tokenValidationResult;
+ }
+ else if (TokenUtilities.IsRecoverableException(tokenValidationResult.Exception))
+ {
+ // If we were still unable to validate, attempt to refresh the configuration and validate using it
+ // but ONLY if the currentConfiguration is not null. We want to avoid refreshing the configuration on
+ // retrieval error as this case should have already been hit before. This refresh handles the case
+ // where a new valid configuration was somehow published during validation time.
+ if (currentConfiguration != null)
+ {
+ validationParameters.ConfigurationManager.RequestRefresh();
+ validationParameters.RefreshBeforeValidation = true;
+ var lastConfig = currentConfiguration;
+ currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
+
+ // Only try to re-validate using the newly obtained config if it doesn't reference equal the previously used configuration.
+ if (lastConfig != currentConfiguration)
+ {
+ tokenValidationResult = jsonWebToken.IsEncrypted ?
+ await ValidateJWEAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false) :
+ await ValidateJWSAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false);
+
+ if (tokenValidationResult.IsValid)
+ {
+ validationParameters.ConfigurationManager.LastKnownGoodConfiguration = currentConfiguration;
+ return tokenValidationResult;
+ }
+ }
+ }
+
+ if (validationParameters.ConfigurationManager.UseLastKnownGoodConfiguration)
+ {
+ validationParameters.RefreshBeforeValidation = false;
+ validationParameters.ValidateWithLKG = true;
+ var recoverableException = tokenValidationResult.Exception;
+
+ foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfigurations())
+ {
+ if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(jsonWebToken.Kid, currentConfiguration, lkgConfiguration, recoverableException))
+ {
+ tokenValidationResult = jsonWebToken.IsEncrypted ?
+ await ValidateJWEAsync(jsonWebToken, validationParameters, lkgConfiguration).ConfigureAwait(false) :
+ await ValidateJWSAsync(jsonWebToken, validationParameters, lkgConfiguration).ConfigureAwait(false);
+
+ if (tokenValidationResult.IsValid)
+ return tokenValidationResult;
+ }
+ }
+ }
+ }
+ }
+
+ return tokenValidationResult;
+ }
+
+ internal async ValueTask ValidateTokenPayloadAsync(
+ JsonWebToken jsonWebToken,
+ TokenValidationParameters validationParameters,
+ BaseConfiguration configuration)
+ {
+ var expires = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Exp) ? (DateTime?)jsonWebToken.ValidTo : null;
+ var notBefore = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Nbf) ? (DateTime?)jsonWebToken.ValidFrom : null;
+
+ Validators.ValidateLifetime(notBefore, expires, jsonWebToken, validationParameters);
+ Validators.ValidateAudience(jsonWebToken.Audiences, jsonWebToken, validationParameters);
+ string issuer = await Validators.ValidateIssuerAsync(jsonWebToken.Issuer, jsonWebToken, validationParameters, configuration).ConfigureAwait(false);
+
+ Validators.ValidateTokenReplay(expires, jsonWebToken.EncodedToken, validationParameters);
+ if (validationParameters.ValidateActor && !string.IsNullOrWhiteSpace(jsonWebToken.Actor))
+ {
+ // Infinite recursion should not occur here, as the JsonWebToken passed into this method is (1) constructed from a string
+ // AND (2) the signature is successfully validated on it. (1) implies that even if there are nested actor tokens,
+ // they must end at some point since they cannot reference one another. (2) means that the token has a valid signature
+ // and (since issuer validation occurs first) came from a trusted authority.
+ // NOTE: More than one nested actor token should not be considered a valid token, but if we somehow encounter one,
+ // this code will still work properly.
+ TokenValidationResult tokenValidationResult =
+ await ValidateTokenAsync(jsonWebToken.Actor, validationParameters.ActorValidationParameters ?? validationParameters).ConfigureAwait(false);
+
+ if (!tokenValidationResult.IsValid)
+ return tokenValidationResult;
+ }
+
+ string tokenType = Validators.ValidateTokenType(jsonWebToken.Typ, jsonWebToken, validationParameters);
+ return new TokenValidationResult(jsonWebToken, this, validationParameters.Clone(), issuer)
+ {
+ IsValid = true,
+ TokenType = tokenType
+ };
+ }
+
+ internal async ValueTask ValidateTokenPayloadAsync(
+ JsonWebToken jsonWebToken,
+ TokenValidationParameters validationParameters,
+ CallContext callContext,
+ CancellationToken cancellationToken)
+ {
+ var expires = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Exp) ? (DateTime?)jsonWebToken.ValidTo : null;
+ var notBefore = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Nbf) ? (DateTime?)jsonWebToken.ValidFrom : null;
+
+ Validators.ValidateLifetime(notBefore, expires, jsonWebToken, validationParameters);
+ Validators.ValidateAudience(jsonWebToken.Audiences, jsonWebToken, validationParameters);
+
+ IssuerValidationResult issuerValidationResult = await Validators.ValidateIssuerAsync(
+ jsonWebToken.Issuer,
+ jsonWebToken,
+ validationParameters,
+ callContext,
+ cancellationToken).ConfigureAwait(false);
+
+ if (!issuerValidationResult.IsValid)
+ {
+ return new TokenValidationResult(jsonWebToken, this, validationParameters, issuerValidationResult.Issuer)
+ {
+ IsValid = false,
+ Exception = issuerValidationResult.Exception
+ };
+ }
+
+ Validators.ValidateTokenReplay(expires, jsonWebToken.EncodedToken, validationParameters);
+ if (validationParameters.ValidateActor && !string.IsNullOrWhiteSpace(jsonWebToken.Actor))
+ {
+ // Infinite recursion should not occur here, as the JsonWebToken passed into this method is (1) constructed from a string
+ // AND (2) the signature is successfully validated on it. (1) implies that even if there are nested actor tokens,
+ // they must end at some point since they cannot reference one another. (2) means that the token has a valid signature
+ // and (since issuer validation occurs first) came from a trusted authority.
+ // NOTE: More than one nested actor token should not be considered a valid token, but if we somehow encounter one,
+ // this code will still work properly.
+ TokenValidationResult tokenValidationResult =
+ await ValidateTokenAsync(jsonWebToken.Actor, validationParameters.ActorValidationParameters ?? validationParameters).ConfigureAwait(false);
+
+ if (!tokenValidationResult.IsValid)
+ return tokenValidationResult;
+ }
+
+ string tokenType = Validators.ValidateTokenType(jsonWebToken.Typ, jsonWebToken, validationParameters);
+ return new TokenValidationResult(jsonWebToken, this, validationParameters.Clone(), issuerValidationResult.Issuer)
+ {
+ IsValid = true,
+ TokenType = tokenType
+ };
+ }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs
index cf4ad2e264..43d1999a3c 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs
@@ -3,12 +3,8 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Security.Claims;
-using System.Text;
using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
@@ -174,15 +170,6 @@ public virtual bool CanReadToken(string token)
}
}
- ///
- /// Returns a value that indicates if this handler can validate a .
- ///
- /// 'true', indicating this instance can validate a .
- public virtual bool CanValidateToken
- {
- get { return true; }
- }
-
private static StringComparison GetStringComparisonRuleIf509(SecurityKey securityKey) => (securityKey is X509SecurityKey)
? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
@@ -498,90 +485,6 @@ public override SecurityToken ReadToken(string token)
return ReadJsonWebToken(token);
}
- ///
- /// Validates a JWS or a JWE.
- ///
- /// A 'JSON Web Token' (JWT) in JWS or JWE Compact Serialization Format.
- /// A required for validation.
- /// A
- [Obsolete("`JsonWebTokens.ValidateToken(string, TokenValidationParameters)` has been deprecated and will be removed in a future release. Use `JsonWebTokens.ValidateTokenAsync(string, TokenValidationParameters)` instead. For more information, see https://aka.ms/IdentityModel/7-breaking-changes", false)]
- public virtual TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters)
- {
- return ValidateTokenAsync(token, validationParameters).ConfigureAwait(false).GetAwaiter().GetResult();
- }
-
- ///
- /// Validates a token.
- /// On a validation failure, no exception will be thrown; instead, the exception will be set in the returned TokenValidationResult.Exception property.
- /// Callers should always check the TokenValidationResult.IsValid property to verify the validity of the result.
- ///
- /// The token to be validated.
- /// A required for validation.
- /// A
- ///
- /// TokenValidationResult.Exception will be set to one of the following exceptions if the is invalid.
- /// if is null or empty.
- /// if is null.
- /// 'token.Length' is greater than .
- /// if is not a valid ,
- /// if the validationParameters.TokenReader delegate is not able to parse/read the token as a valid ,
- ///
- public override async Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters)
- {
- if (string.IsNullOrEmpty(token))
- return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(token)), IsValid = false };
-
- if (validationParameters == null)
- return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(validationParameters)), IsValid = false };
-
- if (token.Length > MaximumTokenSizeInBytes)
- return new TokenValidationResult { Exception = LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(TokenLogMessages.IDX10209, LogHelper.MarkAsNonPII(token.Length), LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)))), IsValid = false };
-
- try
- {
- TokenValidationResult result = ReadToken(token, validationParameters);
- if (result.IsValid)
- return await ValidateTokenAsync(result.SecurityToken, validationParameters).ConfigureAwait(false);
-
- return result;
- }
- catch (Exception ex)
- {
- return new TokenValidationResult
- {
- Exception = ex,
- IsValid = false
- };
- }
- }
-
- ///
- public override async Task ValidateTokenAsync(SecurityToken token, TokenValidationParameters validationParameters)
- {
- if (token == null)
- throw LogHelper.LogArgumentNullException(nameof(token));
-
- if (validationParameters == null)
- return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(validationParameters)), IsValid = false };
-
- var jwt = token as JsonWebToken;
- if (jwt == null)
- return new TokenValidationResult { Exception = LogHelper.LogArgumentException(nameof(token), $"{nameof(token)} must be a {nameof(JsonWebToken)}."), IsValid = false };
-
- try
- {
- return await ValidateTokenAsync(jwt, validationParameters).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- return new TokenValidationResult
- {
- Exception = ex,
- IsValid = false
- };
- }
- }
-
///
/// Converts a string into an instance of .
///
@@ -627,453 +530,5 @@ private static TokenValidationResult ReadToken(string token, TokenValidationPara
IsValid = true
};
}
-
- ///
- /// Private method for token validation, responsible for:
- /// (1) Obtaining a configuration from the .
- /// (2) Revalidating using the Last Known Good Configuration (if present), and obtaining a refreshed configuration (if necessary) and revalidating using it.
- ///
- /// The JWT token
- /// The to be used for validation.
- ///
- private async ValueTask ValidateTokenAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters)
- {
- BaseConfiguration currentConfiguration = null;
- if (validationParameters.ConfigurationManager != null)
- {
- try
- {
- currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
- }
-#pragma warning disable CA1031 // Do not catch general exception types
- catch (Exception ex)
-#pragma warning restore CA1031 // Do not catch general exception types
- {
- // The exception is not re-thrown as the TokenValidationParameters may have the issuer and signing key set
- // directly on them, allowing the library to continue with token validation.
- if (LogHelper.IsEnabled(EventLogLevel.Warning))
- LogHelper.LogWarning(LogHelper.FormatInvariant(TokenLogMessages.IDX10261, validationParameters.ConfigurationManager.MetadataAddress, ex.ToString()));
- }
- }
-
- TokenValidationResult tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false);
- if (validationParameters.ConfigurationManager != null)
- {
- if (tokenValidationResult.IsValid)
- {
- // Set current configuration as LKG if it exists.
- if (currentConfiguration != null)
- validationParameters.ConfigurationManager.LastKnownGoodConfiguration = currentConfiguration;
-
- return tokenValidationResult;
- }
- else if (TokenUtilities.IsRecoverableException(tokenValidationResult.Exception))
- {
- // If we were still unable to validate, attempt to refresh the configuration and validate using it
- // but ONLY if the currentConfiguration is not null. We want to avoid refreshing the configuration on
- // retrieval error as this case should have already been hit before. This refresh handles the case
- // where a new valid configuration was somehow published during validation time.
- if (currentConfiguration != null)
- {
- validationParameters.ConfigurationManager.RequestRefresh();
- validationParameters.RefreshBeforeValidation = true;
- var lastConfig = currentConfiguration;
- currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
-
- // Only try to re-validate using the newly obtained config if it doesn't reference equal the previously used configuration.
- if (lastConfig != currentConfiguration)
- {
- tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false);
-
- if (tokenValidationResult.IsValid)
- {
- validationParameters.ConfigurationManager.LastKnownGoodConfiguration = currentConfiguration;
- return tokenValidationResult;
- }
- }
- }
-
- if (validationParameters.ConfigurationManager.UseLastKnownGoodConfiguration)
- {
- validationParameters.RefreshBeforeValidation = false;
- validationParameters.ValidateWithLKG = true;
- var recoverableException = tokenValidationResult.Exception;
-
- foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfigurations())
- {
- if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(jsonWebToken.Kid, currentConfiguration, lkgConfiguration, recoverableException))
- {
- tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, lkgConfiguration).ConfigureAwait(false);
-
- if (tokenValidationResult.IsValid)
- return tokenValidationResult;
- }
- }
- }
- }
- }
-
- return tokenValidationResult;
- }
-
- private ValueTask ValidateTokenAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
- {
- return jsonWebToken.IsEncrypted ?
- ValidateJWEAsync(jsonWebToken, validationParameters, configuration) :
- ValidateJWSAsync(jsonWebToken, validationParameters, configuration);
- }
-
- private async ValueTask ValidateJWSAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
- {
- try
- {
- TokenValidationResult tokenValidationResult;
- if (validationParameters.TransformBeforeSignatureValidation != null)
- jsonWebToken = validationParameters.TransformBeforeSignatureValidation(jsonWebToken, validationParameters) as JsonWebToken;
-
- if (validationParameters.SignatureValidator != null || validationParameters.SignatureValidatorUsingConfiguration != null)
- {
- var validatedToken = ValidateSignatureUsingDelegates(jsonWebToken, validationParameters, configuration);
- tokenValidationResult = await ValidateTokenPayloadAsync(validatedToken, validationParameters, configuration).ConfigureAwait(false);
- Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, validatedToken, validationParameters, configuration);
- }
- else
- {
- if (validationParameters.ValidateSignatureLast)
- {
- tokenValidationResult = await ValidateTokenPayloadAsync(jsonWebToken, validationParameters, configuration).ConfigureAwait(false);
- if (tokenValidationResult.IsValid)
- tokenValidationResult.SecurityToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, configuration);
- }
- else
- {
- var validatedToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, configuration);
- tokenValidationResult = await ValidateTokenPayloadAsync(validatedToken, validationParameters, configuration).ConfigureAwait(false);
- }
- }
-
- return tokenValidationResult;
- }
-#pragma warning disable CA1031 // Do not catch general exception types
- catch (Exception ex)
-#pragma warning restore CA1031 // Do not catch general exception types
- {
- return new TokenValidationResult
- {
- Exception = ex,
- IsValid = false,
- TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jsonWebToken : null
- };
- }
- }
-
- private async ValueTask ValidateJWEAsync(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
- {
- try
- {
- TokenValidationResult tokenValidationResult = ReadToken(DecryptToken(jwtToken, validationParameters, configuration), validationParameters);
- if (!tokenValidationResult.IsValid)
- return tokenValidationResult;
-
- tokenValidationResult = await ValidateJWSAsync(tokenValidationResult.SecurityToken as JsonWebToken, validationParameters, configuration).ConfigureAwait(false);
- if (!tokenValidationResult.IsValid)
- return tokenValidationResult;
-
- jwtToken.InnerToken = tokenValidationResult.SecurityToken as JsonWebToken;
- jwtToken.Payload = (tokenValidationResult.SecurityToken as JsonWebToken).Payload;
- return new TokenValidationResult
- {
- SecurityToken = jwtToken,
- ClaimsIdentityNoLocking = tokenValidationResult.ClaimsIdentityNoLocking,
- IsValid = true,
- TokenType = tokenValidationResult.TokenType
- };
- }
-#pragma warning disable CA1031 // Do not catch general exception types
- catch (Exception ex)
-#pragma warning restore CA1031 // Do not catch general exception types
- {
- return new TokenValidationResult
- {
- Exception = ex,
- IsValid = false,
- TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jwtToken : null
- };
- }
- }
-
- private static JsonWebToken ValidateSignatureUsingDelegates(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
- {
- if (validationParameters.SignatureValidatorUsingConfiguration != null)
- {
- var validatedToken = validationParameters.SignatureValidatorUsingConfiguration(jsonWebToken.EncodedToken, validationParameters, configuration);
- if (validatedToken == null)
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken)));
-
- if (!(validatedToken is JsonWebToken validatedJsonWebToken))
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10506, LogHelper.MarkAsNonPII(typeof(JsonWebToken)), LogHelper.MarkAsNonPII(validatedToken.GetType()), jsonWebToken)));
-
- return validatedJsonWebToken;
- }
- else if (validationParameters.SignatureValidator != null)
- {
- var validatedToken = validationParameters.SignatureValidator(jsonWebToken.EncodedToken, validationParameters);
- if (validatedToken == null)
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken)));
-
- if (!(validatedToken is JsonWebToken validatedJsonWebToken))
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10506, LogHelper.MarkAsNonPII(typeof(JsonWebToken)), LogHelper.MarkAsNonPII(validatedToken.GetType()), jsonWebToken)));
-
- return validatedJsonWebToken;
- }
-
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken)));
- }
-
- private static JsonWebToken ValidateSignatureAndIssuerSecurityKey(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
- {
- JsonWebToken validatedToken = ValidateSignature(jsonWebToken, validationParameters, configuration);
- Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, jsonWebToken, validationParameters, configuration);
-
- return validatedToken;
- }
-
- private async ValueTask ValidateTokenPayloadAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
- {
- var expires = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Exp) ? (DateTime?)jsonWebToken.ValidTo : null;
- var notBefore = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Nbf) ? (DateTime?)jsonWebToken.ValidFrom : null;
-
- Validators.ValidateLifetime(notBefore, expires, jsonWebToken, validationParameters);
- Validators.ValidateAudience(jsonWebToken.Audiences, jsonWebToken, validationParameters);
- string issuer = await Validators.ValidateIssuerAsync(jsonWebToken.Issuer, jsonWebToken, validationParameters, configuration).ConfigureAwait(false);
-
- Validators.ValidateTokenReplay(expires, jsonWebToken.EncodedToken, validationParameters);
- if (validationParameters.ValidateActor && !string.IsNullOrWhiteSpace(jsonWebToken.Actor))
- {
- // Infinite recursion should not occur here, as the JsonWebToken passed into this method is (1) constructed from a string
- // AND (2) the signature is successfully validated on it. (1) implies that even if there are nested actor tokens,
- // they must end at some point since they cannot reference one another. (2) means that the token has a valid signature
- // and (since issuer validation occurs first) came from a trusted authority.
- // NOTE: More than one nested actor token should not be considered a valid token, but if we somehow encounter one,
- // this code will still work properly.
- TokenValidationResult tokenValidationResult =
- await ValidateTokenAsync(jsonWebToken.Actor, validationParameters.ActorValidationParameters ?? validationParameters).ConfigureAwait(false);
-
- if (!tokenValidationResult.IsValid)
- return tokenValidationResult;
- }
-
- string tokenType = Validators.ValidateTokenType(jsonWebToken.Typ, jsonWebToken, validationParameters);
- return new TokenValidationResult(jsonWebToken, this, validationParameters.Clone(), issuer)
- {
- IsValid = true,
- TokenType = tokenType
- };
- }
-
- ///
- /// Validates the JWT signature.
- ///
- private static JsonWebToken ValidateSignature(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
- {
- bool kidMatched = false;
- IEnumerable keys = null;
-
- if (!jwtToken.IsSigned)
- {
- if (validationParameters.RequireSignedTokens)
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10504, jwtToken)));
- else
- return jwtToken;
- }
-
- if (validationParameters.IssuerSigningKeyResolverUsingConfiguration != null)
- {
- keys = validationParameters.IssuerSigningKeyResolverUsingConfiguration(jwtToken.EncodedToken, jwtToken, jwtToken.Kid, validationParameters, configuration);
- }
- else if (validationParameters.IssuerSigningKeyResolver != null)
- {
- keys = validationParameters.IssuerSigningKeyResolver(jwtToken.EncodedToken, jwtToken, jwtToken.Kid, validationParameters);
- }
- else
- {
- var key = JwtTokenUtilities.ResolveTokenSigningKey(jwtToken.Kid, jwtToken.X5t, validationParameters, configuration);
- if (key != null)
- {
- kidMatched = true;
- keys = [key];
- }
- }
-
- if (validationParameters.TryAllIssuerSigningKeys && keys.IsNullOrEmpty())
- {
- // control gets here if:
- // 1. User specified delegate: IssuerSigningKeyResolver returned null
- // 2. ResolveIssuerSigningKey returned null
- // Try all the keys. This is the degenerate case, not concerned about perf.
- keys = TokenUtilities.GetAllSigningKeys(configuration, validationParameters);
- }
-
- // keep track of exceptions thrown, keys that were tried
- StringBuilder exceptionStrings = null;
- StringBuilder keysAttempted = null;
- var kidExists = !string.IsNullOrEmpty(jwtToken.Kid);
-
- if (keys != null)
- {
- foreach (var key in keys)
- {
- try
- {
- if (ValidateSignature(jwtToken, key, validationParameters))
- {
- if (LogHelper.IsEnabled(EventLogLevel.Informational))
- LogHelper.LogInformation(TokenLogMessages.IDX10242, jwtToken);
-
- jwtToken.SigningKey = key;
- return jwtToken;
- }
- }
- catch (Exception ex)
- {
- (exceptionStrings ??= new StringBuilder()).AppendLine(ex.ToString());
- }
-
- if (key != null)
- {
- (keysAttempted ??= new StringBuilder()).Append(key.ToString()).Append(" , KeyId: ").AppendLine(key.KeyId);
- if (kidExists && !kidMatched && key.KeyId != null)
- kidMatched = jwtToken.Kid.Equals(key.KeyId, key is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
- }
- }
- }
-
- // Get information on where keys used during token validation came from for debugging purposes.
- var keysInTokenValidationParameters = TokenUtilities.GetAllSigningKeys(validationParameters: validationParameters);
- var keysInConfiguration = TokenUtilities.GetAllSigningKeys(configuration);
- var numKeysInTokenValidationParameters = keysInTokenValidationParameters.Count();
- var numKeysInConfiguration = keysInConfiguration.Count();
-
- if (kidExists)
- {
- if (kidMatched)
- {
- JsonWebToken localJwtToken = jwtToken; // avoid closure on non-exceptional path
- var isKidInTVP = keysInTokenValidationParameters.Any(x => x.KeyId.Equals(localJwtToken.Kid));
- var keyLocation = isKidInTVP ? "TokenValidationParameters" : "Configuration";
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10511,
- LogHelper.MarkAsNonPII((object)keysAttempted ?? ""),
- LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters),
- LogHelper.MarkAsNonPII(numKeysInConfiguration),
- LogHelper.MarkAsNonPII(keyLocation),
- LogHelper.MarkAsNonPII(jwtToken.Kid),
- (object)exceptionStrings ?? "",
- jwtToken)));
- }
-
- if (!validationParameters.ValidateSignatureLast)
- {
- InternalValidators.ValidateAfterSignatureFailed(
- jwtToken,
- jwtToken.ValidFromNullable,
- jwtToken.ValidToNullable,
- jwtToken.Audiences,
- validationParameters,
- configuration);
- }
- }
-
- if (keysAttempted is not null)
- {
- if (kidExists)
- {
- throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10503,
- LogHelper.MarkAsNonPII(jwtToken.Kid),
- LogHelper.MarkAsNonPII((object)keysAttempted ?? ""),
- LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters),
- LogHelper.MarkAsNonPII(numKeysInConfiguration),
- (object)exceptionStrings ?? "",
- jwtToken)));
- }
- else
- {
- throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10517,
- LogHelper.MarkAsNonPII((object)keysAttempted ?? ""),
- LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters),
- LogHelper.MarkAsNonPII(numKeysInConfiguration),
- (object)exceptionStrings ?? "",
- jwtToken)));
- }
- }
-
- throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(TokenLogMessages.IDX10500));
- }
-
- internal static bool IsSignatureValid(byte[] signatureBytes, int signatureBytesLength, SignatureProvider signatureProvider, byte[] dataToVerify, int dataToVerifyLength)
- {
- if (signatureProvider is SymmetricSignatureProvider)
- {
- return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, signatureBytes, 0, signatureBytesLength);
- }
- else
- {
- if (signatureBytes.Length == signatureBytesLength)
- {
- return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, signatureBytes, 0, signatureBytesLength);
- }
- else
- {
- byte[] sigBytes = new byte[signatureBytesLength];
- Array.Copy(signatureBytes, 0, sigBytes, 0, signatureBytesLength);
- return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, sigBytes, 0, signatureBytesLength);
- }
- }
- }
-
- internal static bool ValidateSignature(byte[] bytes, int len, string stringWithSignature, int signatureStartIndex, SignatureProvider signatureProvider)
- {
- return Base64UrlEncoding.Decode(
- stringWithSignature,
- signatureStartIndex + 1,
- stringWithSignature.Length - signatureStartIndex - 1,
- signatureProvider,
- bytes,
- len,
- IsSignatureValid);
- }
-
- internal static bool ValidateSignature(JsonWebToken jsonWebToken, SecurityKey key, TokenValidationParameters validationParameters)
- {
- var cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory;
- if (!cryptoProviderFactory.IsSupportedAlgorithm(jsonWebToken.Alg, key))
- {
- if (LogHelper.IsEnabled(EventLogLevel.Informational))
- LogHelper.LogInformation(LogMessages.IDX14000, LogHelper.MarkAsNonPII(jsonWebToken.Alg), key);
-
- return false;
- }
-
- Validators.ValidateAlgorithm(jsonWebToken.Alg, key, jsonWebToken, validationParameters);
- var signatureProvider = cryptoProviderFactory.CreateForVerifying(key, jsonWebToken.Alg);
- try
- {
- if (signatureProvider == null)
- throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(TokenLogMessages.IDX10636, key == null ? "Null" : key.ToString(), LogHelper.MarkAsNonPII(jsonWebToken.Alg))));
-
- return EncodingUtils.PerformEncodingDependentOperation(
- jsonWebToken.EncodedToken,
- 0,
- jsonWebToken.Dot2,
- Encoding.UTF8,
- jsonWebToken.EncodedToken,
- jsonWebToken.Dot2,
- signatureProvider,
- ValidateSignature);
- }
- finally
- {
- cryptoProviderFactory.ReleaseSignatureProvider(signatureProvider);
- }
- }
}
}
diff --git a/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenException.cs b/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenException.cs
index f544c99e93..e346c7cf73 100644
--- a/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenException.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenException.cs
@@ -2,18 +2,22 @@
// Licensed under the MIT License.
using System;
+using System.Diagnostics;
using System.Runtime.Serialization;
+using System.Text;
using Microsoft.IdentityModel.Logging;
namespace Microsoft.IdentityModel.Tokens
{
-
///
/// Represents a security token exception.
///
[Serializable]
public class SecurityTokenException : Exception
{
+ [NonSerialized]
+ private string _stackTrace;
+
///
/// Initializes a new instance of the class.
///
@@ -55,6 +59,49 @@ protected SecurityTokenException(SerializationInfo info, StreamingContext contex
{
}
+ ///
+ /// Gets the stack trace that is captured when the exception is created.
+ ///
+ public override string StackTrace
+ {
+ get
+ {
+ if (_stackTrace == null)
+ {
+ if (ExceptionDetail == null)
+ return base.StackTrace;
+#if NET8_0_OR_GREATER
+ _stackTrace = new StackTrace(ExceptionDetail.StackFrames).ToString();
+#else
+ StringBuilder sb = new();
+ foreach (StackFrame frame in ExceptionDetail.StackFrames)
+ {
+ sb.Append(frame.ToString());
+ sb.Append(Environment.NewLine);
+ }
+
+ _stackTrace = sb.ToString();
+#endif
+ }
+
+ return _stackTrace;
+ }
+ }
+
+ ///
+ /// Gets or sets the source of the exception.
+ ///
+ public override string Source
+ {
+ get => base.Source;
+ set => base.Source = value;
+ }
+
+ internal ExceptionDetail ExceptionDetail
+ {
+ get; set;
+ }
+
#if NET472 || NETSTANDARD2_0 || NET6_0_OR_GREATER
///
/// When overridden in a derived class, sets the System.Runtime.Serialization.SerializationInfo
diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs
index b9fe6c2996..679fbf2b8f 100644
--- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs
+++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs
@@ -35,7 +35,7 @@ internal static class LogMessages
public const string IDX10207 = "IDX10207: Unable to validate audience. The 'audiences' parameter is null.";
public const string IDX10208 = "IDX10208: Unable to validate audience. validationParameters.ValidAudience is null or whitespace and validationParameters.ValidAudiences is null.";
public const string IDX10209 = "IDX10209: Token has length: '{0}' which is larger than the MaximumTokenSizeInBytes: '{1}'.";
- public const string IDX10211 = "IDX10211: Unable to validate issuer. The 'issuer' parameter is null or whitespace";
+ public const string IDX10211 = "IDX10211: Unable to validate issuer. The 'issuer' parameter is null or whitespace.";
public const string IDX10214 = "IDX10214: Audience validation failed. Audiences: '{0}'. Did not match: validationParameters.ValidAudience: '{1}' or validationParameters.ValidAudiences: '{2}'.";
public const string IDX10222 = "IDX10222: Lifetime validation failed. The token is not yet valid. ValidFrom (UTC): '{0}', Current time (UTC): '{1}'.";
public const string IDX10223 = "IDX10223: Lifetime validation failed. The token is expired. ValidTo (UTC): '{0}', Current time (UTC): '{1}'.";
@@ -79,6 +79,8 @@ internal static class LogMessages
//public const string IDX10263 = "IDX10263: Unable to re-validate with ConfigurationManager.LastKnownGoodConfiguration as it is expired.";
public const string IDX10264 = "IDX10264: Reading issuer signing keys from validation parameters and configuration.";
public const string IDX10265 = "IDX10265: Reading issuer signing keys from configuration.";
+ //public const string IDX10266 = "IDX10266: Unable to validate issuer. validationParameters.ValidIssuer is null or whitespace, validationParameters.ValidIssuers is null or empty and ConfigurationManager is null.";
+
// 10500 - SignatureValidation
public const string IDX10500 = "IDX10500: Signature validation failed. No security keys were provided to validate the signature.";
diff --git a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
index bb68764a6d..2112772699 100644
--- a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
+++ b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
@@ -13,7 +13,7 @@ namespace Microsoft.IdentityModel.Tokens
///
/// Contains a set of parameters that are used by a when validating a .
///
- public class TokenValidationParameters
+ public partial class TokenValidationParameters
{
private string _authenticationType;
private TimeSpan _clockSkew = DefaultClockSkew;
@@ -38,7 +38,7 @@ public class TokenValidationParameters
/// Default for the maximum token size.
///
/// 250 KB (kilobytes).
- public const Int32 DefaultMaximumTokenSizeInBytes = 1024 * 250;
+ public const int DefaultMaximumTokenSizeInBytes = 1024 * 250;
///
/// Copy constructor for .
@@ -66,6 +66,7 @@ protected TokenValidationParameters(TokenValidationParameters other)
IssuerSigningKeyValidatorUsingConfiguration = other.IssuerSigningKeyValidatorUsingConfiguration;
IssuerValidator = other.IssuerValidator;
IssuerValidatorAsync = other.IssuerValidatorAsync;
+ IssuerValidationDelegateAsync = other.IssuerValidationDelegateAsync;
IssuerValidatorUsingConfiguration = other.IssuerValidatorUsingConfiguration;
LifetimeValidator = other.LifetimeValidator;
LogTokenId = other.LogTokenId;
@@ -172,22 +173,6 @@ public string AuthenticationType
}
}
- /////
- ///// Gets or sets the for validating X509Certificate2(s).
- /////
- //public X509CertificateValidator CertificateValidator
- //{
- // get
- // {
- // return _certificateValidator;
- // }
-
- // set
- // {
- // _certificateValidator = value;
- // }
- //}
-
///
/// Gets or sets the clock skew to apply when validating a time.
///
@@ -368,7 +353,6 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken,
///
public IssuerValidator IssuerValidator { get; set; }
-
///
/// Gets or sets a delegate that will be used to validate the issuer of the token.
///
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/AsyncValidate.cd b/src/Microsoft.IdentityModel.Tokens/Validation/AsyncValidate.cd
new file mode 100644
index 0000000000..ebefb90859
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/AsyncValidate.cd
@@ -0,0 +1,81 @@
+
+
+
+
+
+ ABEAIAABEEAAEAIAAAAAAAABEQAAAEEACABAAAAkIoA=
+ Validation\TokenValidationResult.cs
+
+
+
+
+
+ AAEAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAA=
+ Validation\IssuerValidationResult.cs
+
+
+
+
+
+ AAAEAAAAAAAAAAAAAAAAEAAEAAAAAAAAAEAABAAAAAA=
+ Validation\ExceptionDetail.cs
+
+
+
+
+
+
+
+
+ AIAAAAJAAAAAAAAAAAgAIAABAAgAAAAABEBBAAAAAAA=
+ Validation\ValidationResult.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEAAA=
+ Validation\LogDetail.cs
+
+
+
+
+
+
+
+
+
+
+
+ AAAIAAAAAAAAAAAAAAIAAAQAAABAQAAAAAAAAAAAAAA=
+ Validation\ValidationFailureType.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAEIAAIAAAAAA=
+ Validation\MessageDetail.cs
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ CallContext.cs
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ExceptionDetail.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ExceptionDetail.cs
new file mode 100644
index 0000000000..1ab311c9e8
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/ExceptionDetail.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// Contains information so that Exceptions can be logged or thrown written as required.
+ ///
+ internal class ExceptionDetail
+ {
+ ///
+ /// Creates an instance of
+ ///
+ /// contains information about the exception that is used to generate the exception message.
+ /// is the type of exception that occurred.
+ /// contains information about the stack frame where the exception occurred.
+ public ExceptionDetail(MessageDetail messageDetail, Type exceptionType, StackFrame stackFrame)
+ : this(messageDetail, exceptionType, stackFrame, null)
+ {
+ }
+
+ ///
+ /// Creates an instance of
+ ///
+ /// contains information about the exception that is used to generate the exception message.
+ /// is the type of exception that occurred.
+ /// contains information about the stack frame where the exception occurred.
+ /// is the inner exception that occurred.
+ public ExceptionDetail(MessageDetail messageDetail, Type exceptionType, StackFrame stackFrame, Exception innerException)
+ {
+ ExceptionType = exceptionType;
+ InnerException = innerException;
+ MessageDetail = messageDetail;
+ StackFrames.Add(stackFrame);
+ }
+
+ ///
+ /// Creates an instance of an using
+ ///
+ /// An instantance of an Exception.
+ public Exception GetException()
+ {
+ if (InnerException != null)
+ return Activator.CreateInstance(ExceptionType, MessageDetail.Message, InnerException) as Exception;
+
+ return Activator.CreateInstance(ExceptionType, MessageDetail.Message) as Exception;
+ }
+
+ ///
+ /// Gets the type of exception that occurred.
+ ///
+ public Type ExceptionType { get; }
+
+ ///
+ /// Gets the inner exception that occurred.
+ ///
+ public Exception InnerException { get; }
+
+ ///
+ /// Gets the message details that are used to generate the exception message.
+ ///
+ public MessageDetail MessageDetail { get; }
+
+ ///
+ /// Gets the stack frames where the exception occurred.
+ ///
+ public IList StackFrames { get; } = [];
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Exceptions.cd b/src/Microsoft.IdentityModel.Tokens/Validation/Exceptions.cd
new file mode 100644
index 0000000000..cd69bb66db
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/Exceptions.cd
@@ -0,0 +1,35 @@
+
+
+
+
+
+ AIAAAAAAAgAAAgAAAAQAAAAAAAAAAAAAAAAAAAAAAAA=
+ Exceptions\SecurityTokenException.cs
+
+
+
+
+
+
+
+
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ Exceptions\SecurityTokenValidationException.cs
+
+
+
+
+
+ AAgAAEAAAAAAAAAAAAACAAAgAAAAAAAAAAAAAAAAAAA=
+ Exceptions\SecurityTokenInvalidIssuerException.cs
+
+
+
+
+
+ AAAEAAAAAAAAAAAAAAAAEAAEAAAAAAAAAEAABAAAAAA=
+ Validation\ExceptionDetail.cs
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/IssuerValidationResult.cs b/src/Microsoft.IdentityModel.Tokens/Validation/IssuerValidationResult.cs
new file mode 100644
index 0000000000..35c53dc561
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/IssuerValidationResult.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// Contains the result of validating a issuer.
+ /// The contains a collection of for each step in the token validation.
+ ///
+ internal class IssuerValidationResult : ValidationResult
+ {
+ private Exception _exception;
+
+ ///
+ /// Creates an instance of
+ ///
+ /// is the issuer that was validated successfully.
+ public IssuerValidationResult(string issuer)
+ : base(ValidationFailureType.ValidationSucceeded)
+ {
+ Issuer = issuer;
+ IsValid = true;
+ }
+
+ ///
+ /// Creates an instance of
+ ///
+ /// is the issuer that was intended to be validated.
+ /// is the that occurred during validation.
+ /// is the that occurred during validation.
+ public IssuerValidationResult(string issuer, ValidationFailureType validationFailure, ExceptionDetail exceptionDetail)
+ : base(validationFailure, exceptionDetail)
+ {
+ Issuer = issuer;
+ IsValid = false;
+ }
+
+ ///
+ /// Gets the that occurred during validation.
+ ///
+ public override Exception Exception
+ {
+ get
+ {
+ if (_exception != null || ExceptionDetail == null)
+ return _exception;
+
+ HasValidOrExceptionWasRead = true;
+ _exception = ExceptionDetail.GetException();
+ SecurityTokenInvalidIssuerException securityTokenInvalidIssuerException = _exception as SecurityTokenInvalidIssuerException;
+ if (securityTokenInvalidIssuerException != null)
+ {
+ securityTokenInvalidIssuerException.InvalidIssuer = Issuer;
+ securityTokenInvalidIssuerException.ExceptionDetail = ExceptionDetail;
+ securityTokenInvalidIssuerException.Source = "Microsoft.IdentityModel.Tokens";
+ }
+
+ return _exception;
+ }
+ }
+
+ ///
+ /// Gets the issuer that was validated or intended to be validated.
+ ///
+ public string Issuer { get; }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/LogDetail.cs b/src/Microsoft.IdentityModel.Tokens/Validation/LogDetail.cs
new file mode 100644
index 0000000000..6b91a67c1e
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/LogDetail.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Microsoft.IdentityModel.Abstractions;
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// Contains information so that logs can be written when needed.
+ ///
+ internal class LogDetail
+ {
+ ///
+ /// Creates an instance of
+ ///
+ /// contains information about the exception that is used to generate the exception message.
+ /// is the level of the event log.
+ public LogDetail(MessageDetail messageDetail, EventLogLevel eventLogLevel)
+ {
+ EventLogLevel = eventLogLevel;
+ MessageDetail = messageDetail;
+ }
+
+ ///
+ /// Gets the level of the event log.
+ ///
+ public EventLogLevel EventLogLevel { get; }
+
+ ///
+ /// Gets the message detail.
+ ///
+ public MessageDetail MessageDetail { get; }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/MessageDetail.cs b/src/Microsoft.IdentityModel.Tokens/Validation/MessageDetail.cs
new file mode 100644
index 0000000000..800126a725
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/MessageDetail.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using Microsoft.IdentityModel.Logging;
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// Contains information about a message that is used to generate a message for logging or exceptions.
+ ///
+ internal class MessageDetail
+ {
+ private string _message;
+
+ // TODO - remove the need to create NonPII objects, we could use tuples where bool == true => object is PII.
+ // TODO - does this need to be ReadOnlyMemory?
+ ///
+ /// Creates an instance of
+ ///
+ /// The message to be formated.
+ /// The parameters for formatting.
+ public MessageDetail(string formatString, params object[] parameters)
+ {
+ // TODO - paramter validation.
+ FormatString = formatString;
+ Parameters = parameters;
+ }
+
+ ///
+ /// Gets the formatted message.
+ ///
+ public string Message
+ {
+ get
+ {
+ _message ??= LogHelper.FormatInvariant(FormatString, Parameters);
+ return _message;
+ }
+ }
+
+ private string FormatString { get; }
+
+ private object[] Parameters { get; }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationParameters.IssuerValidationDelegate.cs b/src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationParameters.IssuerValidationDelegate.cs
new file mode 100644
index 0000000000..cab11a6bbc
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationParameters.IssuerValidationDelegate.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// partial class for the IssuerValidation delegate.
+ ///
+ public partial class TokenValidationParameters
+ {
+ ///
+ /// Gets or sets a delegate that will be used to validate the issuer of a .
+ ///
+ internal IssuerValidationDelegateAsync IssuerValidationDelegateAsync { get; set; }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/TokenValidationResult.cs b/src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationResult.cs
similarity index 85%
rename from src/Microsoft.IdentityModel.Tokens/TokenValidationResult.cs
rename to src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationResult.cs
index 6714c71b9e..46c90738e0 100644
--- a/src/Microsoft.IdentityModel.Tokens/TokenValidationResult.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationResult.cs
@@ -19,10 +19,6 @@ public class TokenValidationResult
private readonly TokenValidationParameters _validationParameters;
private readonly TokenHandler _tokenHandler;
- private Exception _exception;
- private bool _hasIsValidOrExceptionBeenRead = false;
- private bool _isValid = false;
-
// Fields lazily initialized in a thread-safe manner. _claimsIdentity is protected by the _claimsIdentitySyncObj
// lock, and since null is a valid initialized value, _claimsIdentityInitialized tracks whether or not it's valid.
// _claims is constructed by reading the data from the ClaimsIdentity and is synchronized using Interlockeds
@@ -37,6 +33,11 @@ public class TokenValidationResult
private ClaimsIdentity _claimsIdentity;
private Dictionary _claims;
private Dictionary _propertyBag;
+ // TODO - lazy creation of _validationResults
+ private List _validationResults = [];
+
+ private Exception _exception;
+ private bool _isValid;
///
/// Creates an instance of
@@ -60,6 +61,18 @@ internal TokenValidationResult(SecurityToken securityToken, TokenHandler tokenHa
SecurityToken = securityToken;
}
+ ///
+ /// Adds a to the list of .
+ ///
+ /// the associated with one of the validation steps. For example .
+ internal void AddValidationResult(ValidationResult validationResult)
+ {
+ if (validationResult is null)
+ throw LogHelper.LogArgumentNullException(nameof(validationResult));
+
+ _validationResults.Add(validationResult);
+ }
+
///
/// The created from the validated security token.
///
@@ -67,7 +80,7 @@ public IDictionary Claims
{
get
{
- if (!_hasIsValidOrExceptionBeenRead)
+ if (!HasValidOrExceptionWasRead)
LogHelper.LogWarning(LogMessages.IDX10109);
if (_claims is null && ClaimsIdentity is { } ci)
@@ -162,7 +175,7 @@ public Exception Exception
{
get
{
- _hasIsValidOrExceptionBeenRead = true;
+ HasValidOrExceptionWasRead = true;
return _exception;
}
set
@@ -171,6 +184,8 @@ public Exception Exception
}
}
+ internal bool HasValidOrExceptionWasRead { get; set; }
+
///
/// Gets or sets the issuer that was found in the token.
///
@@ -183,7 +198,7 @@ public bool IsValid
{
get
{
- _hasIsValidOrExceptionBeenRead = true;
+ HasValidOrExceptionWasRead = true;
return _isValid;
}
set
@@ -228,5 +243,19 @@ public bool IsValid
/// (e.g for a JSON Web Token, from the "typ" header).
///
public string TokenType { get; set; }
+
+ ///
+ /// Gets the list of that contains the result of validating the token.
+ ///
+ internal IReadOnlyList ValidationResults
+ {
+ get
+ {
+ if (_validationResults is null)
+ Interlocked.CompareExchange(ref _validationResults, new List(), null);
+
+ return _validationResults;
+ }
+ }
}
}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationDelegates.cd b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationDelegates.cd
new file mode 100644
index 0000000000..a6af5bfb82
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationDelegates.cd
@@ -0,0 +1,21 @@
+
+
+
+
+
+ YAgEEAQAKMQck0AAi5R6AACRWgBkBQIAAQgYQsaIkxA=
+ Validation\TokenValidationParameters.IssuerValidationDelegate.cs
+
+
+
+
+
+
+
+
+ AAAAAAAAAACAAAAAACAQBAAAAAAAAAAAgAAAAAAAAAA=
+ Validation\Validators.Issuer.cs
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs
new file mode 100644
index 0000000000..3d872bb63a
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// The type of the failure that occurred when validating a .
+ ///
+ internal abstract class ValidationFailureType
+ {
+ ///
+ /// Creates an instance of
+ ///
+ protected ValidationFailureType(string name)
+ {
+ Name = name;
+ }
+
+ ///
+ /// Gets the name of the .
+ ///
+ public string Name { get; }
+
+ ///
+ /// Defines a type that represents a required parameter was null.
+ ///
+ public static readonly ValidationFailureType NullArgument = new NullArgumentFailure("NullArgument");
+ private class NullArgumentFailure : ValidationFailureType { internal NullArgumentFailure(string name) : base(name) { } }
+
+ ///
+ /// Defines a type that represents that issuer validation failed.
+ ///
+ public static readonly ValidationFailureType IssuerValidationFailed = new IssuerValidationFailure("IssuerValidationFailed");
+ private class IssuerValidationFailure : ValidationFailureType { internal IssuerValidationFailure(string name) : base(name) { } }
+
+ ///
+ /// Defines a type that represents that no evaluation has taken place.
+ ///
+ public static readonly ValidationFailureType ValidationNotEvaluated = new NotEvaluated("NotEvaluated");
+ private class NotEvaluated : ValidationFailureType { internal NotEvaluated(string name) : base(name) { } }
+
+ ///
+ /// Defines a type that represents that no evaluation has taken place.
+ ///
+ public static readonly ValidationFailureType ValidationSucceeded = new Succeeded("Succeeded");
+ private class Succeeded : ValidationFailureType { internal Succeeded(string name) : base(name) { } }
+
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationResult.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationResult.cs
new file mode 100644
index 0000000000..f4d8ce0dc0
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationResult.cs
@@ -0,0 +1,110 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// Contains results of a single step in validating a .
+ /// A maintains a list of for each step in the token validation.
+ ///
+ internal abstract class ValidationResult
+ {
+ private bool _isValid = false;
+
+ ///
+ /// Creates an instance of
+ ///
+ protected ValidationResult()
+ {
+ ValidationFailureType = ValidationFailureType.ValidationNotEvaluated;
+ }
+
+ ///
+ /// Creates an instance of
+ ///
+ /// The that occurred during validation.
+ protected ValidationResult(ValidationFailureType validationFailureType)
+ {
+ ValidationFailureType = validationFailureType;
+ }
+
+ ///
+ /// Creates an instance of
+ ///
+ /// The that occurred during validation.
+ /// The representing the that occurred during validation.
+ protected ValidationResult(ValidationFailureType validationFailureType, ExceptionDetail exceptionDetail)
+ {
+ ValidationFailureType = validationFailureType;
+ ExceptionDetail = exceptionDetail;
+ }
+
+ ///
+ /// Adds a new stack frame to the exception details.
+ ///
+ ///
+ public void AddStackFrame(StackFrame stackFrame)
+ {
+ ExceptionDetail.StackFrames.Add(stackFrame);
+ }
+
+ ///
+ /// Gets the that occurred during validation.
+ ///
+ public abstract Exception Exception { get; }
+
+ ///
+ /// Gets the that occurred during validation.
+ ///
+ public ExceptionDetail ExceptionDetail { get; }
+
+ ///
+ /// True if the token was successfully validated, false otherwise.
+ ///
+ public bool IsValid
+ {
+ get
+ {
+ HasValidOrExceptionWasRead = true;
+ return _isValid;
+ }
+ set
+ {
+ _isValid = value;
+ }
+ }
+
+ // TODO - HasValidOrExceptionWasRead, IsValid, Exception are temporary and will be removed when TokenValidationResult derives from ValidationResult.
+ ///
+ /// Gets or sets a boolean recording if IsValid or Exception was called.
+ ///
+ protected bool HasValidOrExceptionWasRead { get; set; }
+
+ ///
+ /// Logs the validation result.
+ ///
+#pragma warning disable CA1822 // Mark members as static
+ public void Log()
+#pragma warning restore CA1822 // Mark members as static
+ {
+ // TODO - Do we need this, how will it work?
+ }
+
+ ///
+ /// Contains any logs that would have been written.
+ ///
+ public IList LogDetails { get; } = new List();
+
+ ///
+ /// Gets the indicating why the validation was not satisfied.
+ ///
+ public ValidationFailureType ValidationFailureType
+ {
+ get;
+ } = ValidationFailureType.ValidationNotEvaluated;
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs
new file mode 100644
index 0000000000..9c41082c53
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs
@@ -0,0 +1,154 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.IdentityModel.Abstractions;
+using Microsoft.IdentityModel.Logging;
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// Partial class for Audience Validation.
+ ///
+ public static partial class Validators
+ {
+ ///
+ /// Determines if the audiences found in a are valid.
+ ///
+ /// The audiences found in the .
+ /// The being validated.
+ /// required for validation.
+ /// If 'validationParameters' is null.
+ /// If 'audiences' is null and is true.
+ /// If is null or whitespace and is null.
+ /// If none of the 'audiences' matched either or one of .
+ /// An EXACT match is required.
+ public static void ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
+ {
+ if (validationParameters == null)
+ throw LogHelper.LogArgumentNullException(nameof(validationParameters));
+
+ if (validationParameters.AudienceValidator != null)
+ {
+ if (!validationParameters.AudienceValidator(audiences, securityToken, validationParameters))
+ throw LogHelper.LogExceptionMessage(
+ new SecurityTokenInvalidAudienceException(
+ LogHelper.FormatInvariant(
+ LogMessages.IDX10231,
+ LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString())))
+ {
+ InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences)
+ });
+
+ return;
+ }
+
+ if (!validationParameters.ValidateAudience)
+ {
+ LogHelper.LogWarning(LogMessages.IDX10233);
+ return;
+ }
+
+ if (audiences == null)
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10207) { InvalidAudience = null });
+
+ if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience) && (validationParameters.ValidAudiences == null))
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10208) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) });
+
+ if (!audiences.Any())
+ throw LogHelper.LogExceptionMessage(
+ new SecurityTokenInvalidAudienceException(LogHelper.FormatInvariant(LogMessages.IDX10206))
+ { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) });
+
+ // create enumeration of all valid audiences from validationParameters
+ IEnumerable validationParametersAudiences;
+
+ if (validationParameters.ValidAudiences == null)
+ validationParametersAudiences = new[] { validationParameters.ValidAudience };
+ else if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience))
+ validationParametersAudiences = validationParameters.ValidAudiences;
+ else
+ validationParametersAudiences = validationParameters.ValidAudiences.Concat(new[] { validationParameters.ValidAudience });
+
+ if (AudienceIsValid(audiences, validationParameters, validationParametersAudiences))
+ return;
+
+ SecurityTokenInvalidAudienceException ex = new SecurityTokenInvalidAudienceException(
+ LogHelper.FormatInvariant(LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(audiences)),
+ LogHelper.MarkAsNonPII(validationParameters.ValidAudience ?? "null"),
+ LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences))))
+ { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) };
+
+ if (!validationParameters.LogValidationExceptions)
+ throw ex;
+
+ throw LogHelper.LogExceptionMessage(ex);
+ }
+
+ private static bool AudienceIsValid(IEnumerable audiences, TokenValidationParameters validationParameters, IEnumerable validationParametersAudiences)
+ {
+ foreach (string tokenAudience in audiences)
+ {
+ if (string.IsNullOrWhiteSpace(tokenAudience))
+ continue;
+
+ foreach (string validAudience in validationParametersAudiences)
+ {
+ if (string.IsNullOrWhiteSpace(validAudience))
+ continue;
+
+ if (AudiencesMatch(validationParameters, tokenAudience, validAudience))
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience));
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static bool AudiencesMatch(TokenValidationParameters validationParameters, string tokenAudience, string validAudience)
+ {
+ if (validAudience.Length == tokenAudience.Length)
+ {
+ if (string.Equals(validAudience, tokenAudience))
+ return true;
+ }
+ else if (validationParameters.IgnoreTrailingSlashWhenValidatingAudience && AudiencesMatchIgnoringTrailingSlash(tokenAudience, validAudience))
+ return true;
+
+ return false;
+ }
+
+ private static bool AudiencesMatchIgnoringTrailingSlash(string tokenAudience, string validAudience)
+ {
+ int length = -1;
+
+ if (validAudience.Length == tokenAudience.Length + 1 && validAudience.EndsWith("/", StringComparison.InvariantCulture))
+ length = validAudience.Length - 1;
+ else if (tokenAudience.Length == validAudience.Length + 1 && tokenAudience.EndsWith("/", StringComparison.InvariantCulture))
+ length = tokenAudience.Length - 1;
+
+ // the length of the audiences is different by more than 1 and neither ends in a "/"
+ if (length == -1)
+ return false;
+
+ if (string.CompareOrdinal(validAudience, 0, tokenAudience, 0, length) == 0)
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs
new file mode 100644
index 0000000000..faca7d71e5
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs
@@ -0,0 +1,293 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.IdentityModel.Abstractions;
+using Microsoft.IdentityModel.Logging;
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// Definition for delegate that will validate the issuer value in a token.
+ ///
+ /// The issuer to validate.
+ /// The that is being validated.
+ /// required for validation.
+ ///
+ ///
+ /// A that contains the results of validating the issuer.
+ /// This delegate is not expected to throw.
+ internal delegate Task IssuerValidationDelegateAsync(
+ string issuer,
+ SecurityToken securityToken,
+ TokenValidationParameters validationParameters,
+ CallContext callContext,
+ CancellationToken cancellationToken);
+
+ ///
+ /// IssuerValidation
+ ///
+ public static partial class Validators
+ {
+ ///
+ /// Determines if an issuer found in a is valid.
+ ///
+ /// The issuer to validate
+ /// The that is being validated.
+ /// required for validation.
+ /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity".
+ /// If 'validationParameters' is null.
+ /// If 'issuer' is null or whitespace and is true.
+ /// If is null or whitespace and is null.
+ /// If 'issuer' failed to matched either or one of .
+ /// An EXACT match is required.
+ public static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
+ {
+ return ValidateIssuer(issuer, securityToken, validationParameters, null);
+ }
+
+ ///
+ /// Determines if an issuer found in a is valid.
+ ///
+ /// The issuer to validate
+ /// The that is being validated.
+ /// required for validation.
+ /// The required for issuer and signing key validation.
+ /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity".
+ /// If 'validationParameters' is null.
+ /// If 'issuer' is null or whitespace and is true.
+ /// If ' configuration' is null.
+ /// If is null or whitespace and is null and is null.
+ /// If 'issuer' failed to matched either or one of or .
+ /// An EXACT match is required.
+ internal static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
+ {
+ ValueTask vt = ValidateIssuerAsync(issuer, securityToken, validationParameters, configuration);
+ return vt.IsCompletedSuccessfully ?
+ vt.Result :
+ vt.AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
+ }
+
+ ///
+ /// Determines if an issuer found in a is valid.
+ ///
+ /// The issuer to validate
+ /// The that is being validated.
+ /// required for validation.
+ /// The required for issuer and signing key validation.
+ /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity".
+ /// If 'validationParameters' is null.
+ /// If 'issuer' is null or whitespace and is true.
+ /// If ' configuration' is null.
+ /// If is null or whitespace and is null and is null.
+ /// If 'issuer' failed to matched either or one of or .
+ /// An EXACT match is required.
+ internal static async ValueTask ValidateIssuerAsync(
+ string issuer,
+ SecurityToken securityToken,
+ TokenValidationParameters validationParameters,
+ BaseConfiguration configuration)
+ {
+ if (validationParameters == null)
+ throw LogHelper.LogArgumentNullException(nameof(validationParameters));
+
+ if (validationParameters.IssuerValidatorAsync != null)
+ return await validationParameters.IssuerValidatorAsync(issuer, securityToken, validationParameters).ConfigureAwait(false);
+
+ if (validationParameters.IssuerValidatorUsingConfiguration != null)
+ return validationParameters.IssuerValidatorUsingConfiguration(issuer, securityToken, validationParameters, configuration);
+
+ if (validationParameters.IssuerValidator != null)
+ return validationParameters.IssuerValidator(issuer, securityToken, validationParameters);
+
+ if (!validationParameters.ValidateIssuer)
+ {
+ LogHelper.LogWarning(LogMessages.IDX10235);
+ return issuer;
+ }
+
+ if (string.IsNullOrWhiteSpace(issuer))
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10211)
+ { InvalidIssuer = issuer });
+
+ // Throw if all possible places to validate against are null or empty
+ if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer)
+ && validationParameters.ValidIssuers.IsNullOrEmpty()
+ && string.IsNullOrWhiteSpace(configuration?.Issuer))
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10204)
+ { InvalidIssuer = issuer });
+
+ if (configuration != null)
+ {
+ if (string.Equals(configuration.Issuer, issuer))
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer));
+
+ return issuer;
+ }
+ }
+
+ if (string.Equals(validationParameters.ValidIssuer, issuer))
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer));
+
+ return issuer;
+ }
+
+ if (validationParameters.ValidIssuers != null)
+ {
+ foreach (string str in validationParameters.ValidIssuers)
+ {
+ if (string.IsNullOrEmpty(str))
+ {
+ LogHelper.LogInformation(LogMessages.IDX10262);
+ continue;
+ }
+
+ if (string.Equals(str, issuer))
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer));
+
+ return issuer;
+ }
+ }
+ }
+
+ SecurityTokenInvalidIssuerException ex = new SecurityTokenInvalidIssuerException(
+ LogHelper.FormatInvariant(LogMessages.IDX10205,
+ LogHelper.MarkAsNonPII(issuer),
+ LogHelper.MarkAsNonPII(validationParameters.ValidIssuer ?? "null"),
+ LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)),
+ LogHelper.MarkAsNonPII(configuration?.Issuer)))
+ { InvalidIssuer = issuer };
+
+ if (!validationParameters.LogValidationExceptions)
+ throw ex;
+
+ throw LogHelper.LogExceptionMessage(ex);
+ }
+
+ ///
+ /// Determines if an issuer found in a is valid.
+ ///
+ /// The issuer to validate
+ /// The that is being validated.
+ /// required for validation.
+ ///
+ ///
+ /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity".
+ /// If 'validationParameters' is null.
+ /// If 'issuer' is null or whitespace and is true.
+ /// If is null or whitespace and is null.
+ /// If 'issuer' failed to matched either or one of .
+ /// An EXACT match is required.
+ internal static async Task ValidateIssuerAsync(
+ string issuer,
+ SecurityToken securityToken,
+ TokenValidationParameters validationParameters,
+ CallContext callContext,
+ CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(issuer))
+ {
+ return new IssuerValidationResult(
+ issuer,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10211,
+ null),
+ typeof(SecurityTokenInvalidIssuerException),
+ new StackFrame(true),
+ null));
+ }
+
+ if (validationParameters == null)
+ throw LogHelper.LogArgumentNullException(nameof(validationParameters));
+
+ if (securityToken == null)
+ throw LogHelper.LogArgumentNullException(nameof(securityToken));
+
+ BaseConfiguration configuration = null;
+ if (validationParameters.ConfigurationManager != null)
+ configuration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(cancellationToken).ConfigureAwait(false);
+
+ // Throw if all possible places to validate against are null or empty
+ if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer)
+ && validationParameters.ValidIssuers.IsNullOrEmpty()
+ && string.IsNullOrWhiteSpace(configuration?.Issuer))
+ {
+ return new IssuerValidationResult(
+ issuer,
+ ValidationFailureType.IssuerValidationFailed,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10211,
+ null),
+ typeof(SecurityTokenInvalidIssuerException),
+ new StackFrame(true)));
+ }
+
+ // TODO - we should distinguish if configuration, TVP.ValidIssuer or TVP.ValidIssuers was used to validate the issuer.
+ if (configuration != null)
+ {
+ if (string.Equals(configuration.Issuer, issuer))
+ {
+ // TODO - how and when to log
+ // Logs will have to be passed back to Wilson
+ // so that they can be written to the correct place and in the correct format respecting PII.
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer), callContext);
+
+ return new IssuerValidationResult(issuer);
+ }
+ }
+
+ if (string.Equals(validationParameters.ValidIssuer, issuer))
+ {
+ return new IssuerValidationResult(issuer);
+ }
+
+ if (validationParameters.ValidIssuers != null)
+ {
+ foreach (string str in validationParameters.ValidIssuers)
+ {
+ if (string.IsNullOrEmpty(str))
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX10262);
+
+ continue;
+ }
+
+ if (string.Equals(str, issuer))
+ {
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer));
+
+ return new IssuerValidationResult(issuer);
+ }
+ }
+ }
+
+ return new IssuerValidationResult(
+ issuer,
+ ValidationFailureType.IssuerValidationFailed,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10205,
+ LogHelper.MarkAsNonPII(issuer),
+ LogHelper.MarkAsNonPII(validationParameters.ValidIssuer ?? "null"),
+ LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)),
+ LogHelper.MarkAsNonPII(configuration?.Issuer)),
+ typeof(SecurityTokenInvalidIssuerException),
+ new StackFrame(true)));
+ }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs
new file mode 100644
index 0000000000..77c553c4db
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using Microsoft.IdentityModel.Logging;
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// IssuerValidation
+ ///
+ public static partial class Validators
+ {
+ ///
+ /// Validates the lifetime of a .
+ ///
+ /// The 'notBefore' time found in the .
+ /// The 'expiration' time found in the .
+ /// The being validated.
+ /// required for validation.
+ /// If 'validationParameters' is null.
+ /// If 'expires.HasValue' is false and is true.
+ /// If 'notBefore' is > 'expires'.
+ /// If 'notBefore' is > DateTime.UtcNow.
+ /// If 'expires' is < DateTime.UtcNow.
+ /// All time comparisons apply .
+ public static void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
+ {
+ if (validationParameters == null)
+ throw LogHelper.LogArgumentNullException(nameof(validationParameters));
+
+ if (validationParameters.LifetimeValidator != null)
+ {
+ if (!validationParameters.LifetimeValidator(notBefore, expires, securityToken, validationParameters))
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidLifetimeException(LogHelper.FormatInvariant(LogMessages.IDX10230, securityToken))
+ { NotBefore = notBefore, Expires = expires });
+
+ return;
+ }
+
+ if (!validationParameters.ValidateLifetime)
+ {
+ LogHelper.LogInformation(LogMessages.IDX10238);
+ return;
+ }
+
+ ValidatorUtilities.ValidateLifetime(notBefore, expires, securityToken, validationParameters);
+ }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs b/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs
index 482f2f3d37..6c652f6a3b 100644
--- a/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs
+++ b/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs
@@ -3,6 +3,7 @@
using System;
using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Abstractions;
namespace Microsoft.IdentityModel.Tokens
{
@@ -49,7 +50,8 @@ internal static void ValidateLifetime(DateTime? notBefore, DateTime? expires, Se
});
// if it reaches here, that means lifetime of the token is valid
- LogHelper.LogInformation(LogMessages.IDX10239);
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX10239);
}
}
}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validators.cs b/src/Microsoft.IdentityModel.Tokens/Validators.cs
index 2c9f9e6ca1..000ba4c617 100644
--- a/src/Microsoft.IdentityModel.Tokens/Validators.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Validators.cs
@@ -2,10 +2,8 @@
// Licensed under the MIT License.
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
-using System.Threading.Tasks;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.IdentityModel.Logging;
@@ -14,7 +12,7 @@ namespace Microsoft.IdentityModel.Tokens
///
/// AudienceValidator
///
- public static class Validators
+ public static partial class Validators
{
///
/// Validates if a given algorithm for a is valid.
@@ -50,283 +48,6 @@ public static void ValidateAlgorithm(string algorithm, SecurityKey securityKey,
}
}
- ///
- /// Determines if the audiences found in a are valid.
- ///
- /// The audiences found in the .
- /// The being validated.
- /// required for validation.
- /// If 'validationParameters' is null.
- /// If 'audiences' is null and is true.
- /// If is null or whitespace and is null.
- /// If none of the 'audiences' matched either or one of .
- /// An EXACT match is required.
- public static void ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
- {
- if (validationParameters == null)
- throw LogHelper.LogArgumentNullException(nameof(validationParameters));
-
- if (validationParameters.AudienceValidator != null)
- {
- if (!validationParameters.AudienceValidator(audiences, securityToken, validationParameters))
- throw LogHelper.LogExceptionMessage(
- new SecurityTokenInvalidAudienceException(
- LogHelper.FormatInvariant(
- LogMessages.IDX10231,
- LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString())))
- {
- InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences)
- });
-
- return;
- }
-
- if (!validationParameters.ValidateAudience)
- {
- LogHelper.LogWarning(LogMessages.IDX10233);
- return;
- }
-
- if (audiences == null)
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10207) { InvalidAudience = null });
-
- if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience) && (validationParameters.ValidAudiences == null))
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10208) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) });
-
- if (!audiences.Any())
- throw LogHelper.LogExceptionMessage(
- new SecurityTokenInvalidAudienceException(LogHelper.FormatInvariant(LogMessages.IDX10206))
- { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) });
-
- // create enumeration of all valid audiences from validationParameters
- IEnumerable validationParametersAudiences;
-
- if (validationParameters.ValidAudiences == null)
- validationParametersAudiences = new[] { validationParameters.ValidAudience };
- else if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience))
- validationParametersAudiences = validationParameters.ValidAudiences;
- else
- validationParametersAudiences = validationParameters.ValidAudiences.Concat(new[] { validationParameters.ValidAudience });
-
- if (AudienceIsValid(audiences, validationParameters, validationParametersAudiences))
- return;
-
- SecurityTokenInvalidAudienceException ex = new SecurityTokenInvalidAudienceException(
- LogHelper.FormatInvariant(LogMessages.IDX10214,
- LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(audiences)),
- LogHelper.MarkAsNonPII(validationParameters.ValidAudience ?? "null"),
- LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences))))
- { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) };
-
- if (!validationParameters.LogValidationExceptions)
- throw ex;
-
- throw LogHelper.LogExceptionMessage(ex);
- }
-
- private static bool AudienceIsValid(IEnumerable audiences, TokenValidationParameters validationParameters, IEnumerable validationParametersAudiences)
- {
- foreach (string tokenAudience in audiences)
- {
- if (string.IsNullOrWhiteSpace(tokenAudience))
- continue;
-
- foreach (string validAudience in validationParametersAudiences)
- {
- if (string.IsNullOrWhiteSpace(validAudience))
- continue;
-
- if (AudiencesMatch(validationParameters, tokenAudience, validAudience))
- {
- if (LogHelper.IsEnabled(EventLogLevel.Informational))
- LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience));
-
- return true;
- }
- }
- }
-
- return false;
- }
-
- private static bool AudiencesMatch(TokenValidationParameters validationParameters, string tokenAudience, string validAudience)
- {
- if (validAudience.Length == tokenAudience.Length)
- {
- if (string.Equals(validAudience, tokenAudience))
- return true;
- }
- else if (validationParameters.IgnoreTrailingSlashWhenValidatingAudience && AudiencesMatchIgnoringTrailingSlash(tokenAudience, validAudience))
- return true;
-
- return false;
- }
-
- private static bool AudiencesMatchIgnoringTrailingSlash(string tokenAudience, string validAudience)
- {
- int length = -1;
-
- if (validAudience.Length == tokenAudience.Length + 1 && validAudience.EndsWith("/", StringComparison.InvariantCulture))
- length = validAudience.Length - 1;
- else if (tokenAudience.Length == validAudience.Length + 1 && tokenAudience.EndsWith("/", StringComparison.InvariantCulture))
- length = tokenAudience.Length - 1;
-
- // the length of the audiences is different by more than 1 and neither ends in a "/"
- if (length == -1)
- return false;
-
- if (string.CompareOrdinal(validAudience, 0, tokenAudience, 0, length) == 0)
- {
- if (LogHelper.IsEnabled(EventLogLevel.Informational))
- LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience));
-
- return true;
- }
-
- return false;
- }
-
- ///
- /// Determines if an issuer found in a is valid.
- ///
- /// The issuer to validate
- /// The that is being validated.
- /// required for validation.
- /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity".
- /// If 'validationParameters' is null.
- /// If 'issuer' is null or whitespace and is true.
- /// If is null or whitespace and is null.
- /// If 'issuer' failed to matched either or one of .
- /// An EXACT match is required.
- public static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
- {
- return ValidateIssuer(issuer, securityToken, validationParameters, null);
- }
-
- ///
- /// Determines if an issuer found in a is valid.
- ///
- /// The issuer to validate
- /// The that is being validated.
- /// required for validation.
- /// The required for issuer and signing key validation.
- /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity".
- /// If 'validationParameters' is null.
- /// If 'issuer' is null or whitespace and is true.
- /// If ' configuration' is null.
- /// If is null or whitespace and is null and is null.
- /// If 'issuer' failed to matched either or one of or .
- /// An EXACT match is required.
- internal static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
- {
- ValueTask vt = ValidateIssuerAsync(issuer, securityToken, validationParameters, configuration);
- return vt.IsCompletedSuccessfully ?
- vt.Result :
- vt.AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
- }
-
- ///
- /// Determines if an issuer found in a is valid.
- ///
- /// The issuer to validate
- /// The that is being validated.
- /// required for validation.
- /// The required for issuer and signing key validation.
- /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity".
- /// If 'validationParameters' is null.
- /// If 'issuer' is null or whitespace and is true.
- /// If ' configuration' is null.
- /// If is null or whitespace and is null and is null.
- /// If 'issuer' failed to matched either or one of or .
- /// An EXACT match is required.
- internal static async ValueTask ValidateIssuerAsync(
- string issuer,
- SecurityToken securityToken,
- TokenValidationParameters validationParameters,
- BaseConfiguration configuration)
- {
- if (validationParameters == null)
- throw LogHelper.LogArgumentNullException(nameof(validationParameters));
-
- if (validationParameters.IssuerValidatorAsync != null)
- return await validationParameters.IssuerValidatorAsync(issuer, securityToken, validationParameters).ConfigureAwait(false);
-
- if (validationParameters.IssuerValidatorUsingConfiguration != null)
- return validationParameters.IssuerValidatorUsingConfiguration(issuer, securityToken, validationParameters, configuration);
-
- if (validationParameters.IssuerValidator != null)
- return validationParameters.IssuerValidator(issuer, securityToken, validationParameters);
-
- if (!validationParameters.ValidateIssuer)
- {
- LogHelper.LogWarning(LogMessages.IDX10235);
- return issuer;
- }
-
- if (string.IsNullOrWhiteSpace(issuer))
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10211)
- { InvalidIssuer = issuer });
-
- // Throw if all possible places to validate against are null or empty
- if ( string.IsNullOrWhiteSpace(validationParameters.ValidIssuer)
- && validationParameters.ValidIssuers.IsNullOrEmpty()
- && string.IsNullOrWhiteSpace(configuration?.Issuer))
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10204)
- { InvalidIssuer = issuer });
-
- if (configuration != null)
- {
- if (string.Equals(configuration.Issuer, issuer))
- {
- if (LogHelper.IsEnabled(EventLogLevel.Informational))
- LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer));
-
- return issuer;
- }
- }
-
- if (string.Equals(validationParameters.ValidIssuer, issuer))
- {
- if (LogHelper.IsEnabled(EventLogLevel.Informational))
- LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer));
-
- return issuer;
- }
-
- if (validationParameters.ValidIssuers != null)
- {
- foreach (string str in validationParameters.ValidIssuers)
- {
- if (string.IsNullOrEmpty(str))
- {
- LogHelper.LogInformation(LogMessages.IDX10262);
- continue;
- }
-
- if (string.Equals(str, issuer))
- {
- if (LogHelper.IsEnabled(EventLogLevel.Informational))
- LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer));
-
- return issuer;
- }
- }
- }
-
- SecurityTokenInvalidIssuerException ex = new SecurityTokenInvalidIssuerException(
- LogHelper.FormatInvariant(LogMessages.IDX10205,
- LogHelper.MarkAsNonPII(issuer),
- LogHelper.MarkAsNonPII(validationParameters.ValidIssuer ?? "null"),
- LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)),
- LogHelper.MarkAsNonPII(configuration?.Issuer)))
- { InvalidIssuer = issuer };
-
- if (!validationParameters.LogValidationExceptions)
- throw ex;
-
- throw LogHelper.LogExceptionMessage(ex);
- }
-
///
/// Validates the that signed a .
///
@@ -422,42 +143,6 @@ internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, T
}
}
- ///
- /// Validates the lifetime of a .
- ///
- /// The 'notBefore' time found in the .
- /// The 'expiration' time found in the .
- /// The being validated.
- /// required for validation.
- /// If 'validationParameters' is null.
- /// If 'expires.HasValue' is false and is true.
- /// If 'notBefore' is > 'expires'.
- /// If 'notBefore' is > DateTime.UtcNow.
- /// If 'expires' is < DateTime.UtcNow.
- /// All time comparisons apply .
- public static void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
- {
- if (validationParameters == null)
- throw LogHelper.LogArgumentNullException(nameof(validationParameters));
-
- if (validationParameters.LifetimeValidator != null)
- {
- if (!validationParameters.LifetimeValidator(notBefore, expires, securityToken, validationParameters))
- throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidLifetimeException(LogHelper.FormatInvariant(LogMessages.IDX10230, securityToken))
- { NotBefore = notBefore, Expires = expires });
-
- return;
- }
-
- if (!validationParameters.ValidateLifetime)
- {
- LogHelper.LogInformation(LogMessages.IDX10238);
- return;
- }
-
- ValidatorUtilities.ValidateLifetime(notBefore, expires, securityToken, validationParameters);
- }
-
///
/// Validates if a token has been replayed.
///
diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs
index f7b1d7b843..bd041cac2f 100644
--- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs
+++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs
@@ -3523,7 +3523,7 @@ public static TheoryData ValidateJwsWithConfigTheoryData
incorrectSigningKeysConfig.SigningKeys.Add(KeyingMaterial.X509SecurityKey2);
theoryData.Add(new JwtTheoryData
{
- TestId = nameof(Default.AsymmetricJws) + "_" + "TVPInvalid" + "_" + "ConfigSigningKeysInvalid" + "_SignatureValidatorReturnsValidToken",
+ TestId = nameof(Default.AsymmetricJws) + "_TVPInvalid_ConfigSigningKeysInvalid_SignatureValidatorReturnsValidToken",
Token = Default.AsymmetricJws,
ValidationParameters = new TokenValidationParameters
{
diff --git a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs
index 753b97efad..872a9e8cea 100644
--- a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs
+++ b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs
@@ -19,6 +19,7 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
+using System.Xml.Linq;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
@@ -60,6 +61,7 @@ public class IdentityComparer
{ typeof(IEnumerable).ToString(), AreX509DataEnumsEqual },
{ typeof(int).ToString(), AreIntsEqual },
{ typeof(IssuerSerial).ToString(), CompareAllPublicProperties },
+ { typeof(IssuerValidationResult).ToString(), AreIssuerValidationResultsEqual },
{ typeof(JArray).ToString(), AreJArraysEqual },
{ typeof(JObject).ToString(), AreJObjectsEqual },
{ typeof(JsonElement).ToString(), AreJsonElementsEqual },
@@ -542,6 +544,66 @@ public static bool AreEqual(object object1, object object2, CompareContext conte
return context.Merge(localContext);
}
+ public static bool AreIssuerValidationResultsEqual(object object1, object object2, CompareContext context)
+ {
+ var localContext = new CompareContext(context);
+ if (!ContinueCheckingEquality(object1, object2, context))
+ return context.Merge(localContext);
+
+ return AreIssuerValidationResultsEqual(
+ object1 as IssuerValidationResult,
+ object2 as IssuerValidationResult,
+ "IssuerValidationResult1",
+ "IssuerValidationResult2",
+ null,
+ context);
+ }
+
+ internal static bool AreIssuerValidationResultsEqual(
+ IssuerValidationResult issuerValidationResult1,
+ IssuerValidationResult issuerValidationResult2,
+ string name1,
+ string name2,
+ string stackPrefix,
+ CompareContext context)
+ {
+ var localContext = new CompareContext(context);
+ if (!ContinueCheckingEquality(issuerValidationResult1, issuerValidationResult2, localContext))
+ return context.Merge(localContext);
+
+ if (issuerValidationResult1.Issuer != issuerValidationResult2.Issuer)
+ localContext.Diffs.Add($"IssuerValidationResult1.Issuer: {issuerValidationResult1.Issuer} != IssuerValidationResult2.Issuer: {issuerValidationResult2.Issuer}");
+
+ // true => both are not null.
+ if (ContinueCheckingEquality(issuerValidationResult1.Exception, issuerValidationResult2.Exception, localContext))
+ {
+ AreStringsEqual(
+ issuerValidationResult1.Exception.Message,
+ issuerValidationResult2.Exception.Message,
+ $"({name1})issuerValidationResult1.Exception.Message",
+ $"({name2})issuerValidationResult1.Exception.Message",
+ localContext);
+
+ AreStringsEqual(
+ issuerValidationResult1.Exception.Source,
+ issuerValidationResult2.Exception.Source,
+ $"({name1})issuerValidationResult1.Exception.Source",
+ $"({name2})issuerValidationResult2.Exception.Source",
+ localContext);
+
+ if (!string.IsNullOrEmpty(stackPrefix))
+ AreStringPrefixesEqual(
+ issuerValidationResult1.Exception.StackTrace.Trim(),
+ issuerValidationResult2.Exception.StackTrace.Trim(),
+ $"({name1})issuerValidationResult1.Exception.StackTrace",
+ $"({name2})issuerValidationResult2.Exception.StackTrace",
+ stackPrefix.Trim(),
+ localContext);
+ }
+
+ return context.Merge(localContext);
+ }
+
public static bool AreJArraysEqual(object object1, object object2, CompareContext context)
{
var localContext = new CompareContext(context);
@@ -1089,15 +1151,43 @@ public static bool AreStringsEqual(object object1, object object2, string name1,
if (!string.Equals(str1, str2, context.StringComparison))
{
- localContext.Diffs.Add($"{name1} != {name2}, StringComparison: '{context.StringComparison}'");
- localContext.Diffs.Add(str1);
+ localContext.Diffs.Add($"'{name1}' != '{name2}', StringComparison: '{context.StringComparison}'");
+ localContext.Diffs.Add($"'{str1}'");
localContext.Diffs.Add($"!=");
- localContext.Diffs.Add(str2);
+ localContext.Diffs.Add($"'{str2}'");
+ }
+
+ return context.Merge(localContext);
+ }
+
+ public static bool AreStringPrefixesEqual(
+ string string1,
+ string string2,
+ string name1,
+ string name2,
+ string prefix,
+ CompareContext context)
+ {
+ var localContext = new CompareContext(context);
+ if (!ContinueCheckingEquality(string1, string2, localContext))
+ return context.Merge(localContext);
+
+ if (!string1.StartsWith(prefix, context.StringComparison))
+ {
+ localContext.Diffs.Add($"'{name1}': does not start with prefix: '{prefix}', StringComparison: '{context.StringComparison}'");
+ localContext.Diffs.Add($"'{string1}'");
+ }
+
+ if (!string2.StartsWith(prefix, context.StringComparison))
+ {
+ localContext.Diffs.Add($"'{name2}': does not start with prefix: '{prefix}', StringComparison: '{context.StringComparison}'");
+ localContext.Diffs.Add($"'{string2}'");
}
return context.Merge(localContext);
}
+
public static bool AreStringEnumDictionariesEqual(IDictionary> dictionary1, IDictionary> dictionary2, CompareContext context)
{
var localContext = new CompareContext(context);
@@ -1316,6 +1406,26 @@ public static bool CompareAllPublicProperties(object obj1, object obj2, CompareC
return context.Merge($"CompareAllPublicProperties: {type}", localContext);
}
+ public static bool IsOnlyOneObjectNull(object object1, object object2, CompareContext context)
+ {
+ if (object1 == null && object2 == null)
+ return false;
+
+ if (object1 == null)
+ {
+ context.Diffs.Add(BuildStringDiff(object2.GetType().ToString(), object1, object2));
+ return true;
+ }
+
+ if (object2 == null)
+ {
+ context.Diffs.Add(BuildStringDiff(object1.GetType().ToString(), object1, object2));
+ return true;
+ }
+
+ return false;
+ }
+
public static bool ContinueCheckingEquality(object obj1, object obj2, CompareContext context)
{
if (obj1 == null && obj2 == null)
diff --git a/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs
index f0663cc997..1170d40822 100644
--- a/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs
+++ b/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs
@@ -87,7 +87,7 @@ public static string IssuerValidatorUsingConfigEcho(string issuer, SecurityToken
return issuer;
}
- public static ValueTask IssuerValidatorAsync(string issuer, SecurityToken token, TokenValidationParameters validationParameters)
+ public static ValueTask IssuerValidatorInternalAsync(string issuer, SecurityToken token, TokenValidationParameters validationParameters)
{
return new ValueTask(issuer);
}
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs
index e78a0391b6..f293582ed3 100644
--- a/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs
@@ -575,9 +575,9 @@ public void CompareStrings()
var string2 = "goodbye";
IdentityComparer.AreEqual(string1, string2, context);
- Assert.True(context.Diffs.Count(s => s == "str1 != str2, StringComparison: 'Ordinal'") == 1);
- Assert.True(context.Diffs[1] == string1);
- Assert.True(context.Diffs[3] == string2);
+ Assert.True(context.Diffs.Count(s => s == "'str1' != 'str2', StringComparison: 'Ordinal'") == 1);
+ Assert.True(context.Diffs[1] == $"'{string1}'");
+ Assert.True(context.Diffs[3] == $"'{string2}'");
}
[Fact]
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Json/JsonUtilities.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Json/JsonUtilities.cs
index dfbc058216..8dd4f023f1 100644
--- a/test/Microsoft.IdentityModel.Tokens.Tests/Json/JsonUtilities.cs
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/Json/JsonUtilities.cs
@@ -7,6 +7,7 @@
using System.Text;
using System.Text.Json;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
+using Microsoft.IdentityModel.JsonWebTokens;
using Newtonsoft.Json.Linq;
namespace Microsoft.IdentityModel.Tokens.Json.Tests
@@ -169,6 +170,11 @@ public static void SetAdditionalDataValues(IDictionary dictionar
dictionary["true"] = true;
}
+ public static JsonWebToken CreateUnsignedJsonWebToken(string key, object value)
+ {
+ return new JsonWebToken(CreateUnsignedToken(key, value));
+ }
+
public static string CreateUnsignedToken(string key, object value)
{
return EmptyHeader + "." + CreateEncodedJson(key, value) + ".";
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs
index 041de6d40d..a44e4c7152 100644
--- a/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs
@@ -15,7 +15,7 @@ namespace Microsoft.IdentityModel.Tokens.Tests
{
public class TokenValidationParametersTests
{
- int ExpectedPropertyCount = 59;
+ int ExpectedPropertyCount = 60;
[Fact]
public void Publics()
@@ -71,6 +71,7 @@ public void Publics()
IssuerSigningKey = issuerSigningKey,
IssuerSigningKeyResolver = (token, securityToken, keyIdentifier, tvp) => { return new List { issuerSigningKey }; },
IssuerSigningKeys = issuerSigningKeys,
+ IssuerValidationDelegateAsync = Validators.ValidateIssuerAsync,
IssuerValidator = ValidationDelegates.IssuerValidatorEcho,
LifetimeValidator = ValidationDelegates.LifetimeValidatorReturnsTrue,
LogTokenId = true,
@@ -290,8 +291,9 @@ private TokenValidationParameters CreateTokenValidationParameters()
validationParameters.IssuerSigningKeyResolverUsingConfiguration = ValidationDelegates.IssuerSigningKeyResolverUsingConfiguration;
validationParameters.IssuerSigningKeyValidator = ValidationDelegates.IssuerSigningKeyValidator;
validationParameters.IssuerSigningKeyValidatorUsingConfiguration = ValidationDelegates.IssuerSigningKeyValidatorUsingConfiguration;
+ validationParameters.IssuerValidationDelegateAsync = Validators.ValidateIssuerAsync;
validationParameters.IssuerValidator = ValidationDelegates.IssuerValidatorEcho;
- validationParameters.IssuerValidatorAsync = ValidationDelegates.IssuerValidatorAsync;
+ validationParameters.IssuerValidatorAsync = ValidationDelegates.IssuerValidatorInternalAsync;
validationParameters.IssuerValidatorUsingConfiguration = ValidationDelegates.IssuerValidatorUsingConfigEcho;
validationParameters.LifetimeValidator = ValidationDelegates.LifetimeValidatorReturnsTrue;
validationParameters.NameClaimTypeRetriever = ValidationDelegates.NameClaimTypeRetriever;
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/AsyncValidatorsTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/AsyncValidatorsTests.cs
new file mode 100644
index 0000000000..f5bacfc0b7
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/AsyncValidatorsTests.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.IdentityModel.TestUtils;
+using Xunit;
+
+namespace Microsoft.IdentityModel.Tokens.Validation.Tests
+{
+ public class AsyncValidatorTests
+ {
+ [Theory, MemberData(nameof(AsyncIssuerValidatorTestCases))]
+ public async Task AsyncIssuerValidatorTests(IssuerValidatorTheoryData theoryData)
+ {
+ CompareContext context = TestUtilities.WriteHeader($"{this}.AsyncIssuerValidatorTests", theoryData);
+ try
+ {
+ IssuerValidationResult result = await Validators.ValidateIssuerAsync(
+ theoryData.Issuer,
+ theoryData.SecurityToken,
+ theoryData.ValidationParameters,
+ null,
+ CancellationToken.None).ConfigureAwait(false);
+ Exception exception = result.Exception;
+ context.Diffs.Add("Exception: " + exception.ToString());
+ }
+ catch (Exception ex)
+ {
+ context.Diffs.Add("Exception: " + ex.ToString());
+ }
+ }
+
+ public static TheoryData AsyncIssuerValidatorTestCases
+ {
+ get
+ {
+ TheoryData theoryData = new TheoryData();
+
+ theoryData.Add(new IssuerValidatorTheoryData
+ {
+ Issuer = null,
+ ValidationParameters = new TokenValidationParameters(),
+ });
+
+ return theoryData;
+ }
+ }
+ }
+
+ public class IssuerValidatorTheoryData : TheoryDataBase
+ {
+ public string Issuer { get; set; }
+ public TokenValidationParameters ValidationParameters { get; set; }
+ public SecurityToken SecurityToken { get; set; }
+ }
+}
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/IssuerValidationResultTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/IssuerValidationResultTests.cs
new file mode 100644
index 0000000000..3c3f403fa2
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/IssuerValidationResultTests.cs
@@ -0,0 +1,120 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.IdentityModel.JsonWebTokens;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.TestUtils;
+using Microsoft.IdentityModel.Tokens.Json.Tests;
+using Xunit;
+
+namespace Microsoft.IdentityModel.Tokens.Validation.Tests
+{
+ public class IssuerValidationResultTests
+ {
+ [Theory, MemberData(nameof(IssuerValdationResultsTestCases), DisableDiscoveryEnumeration = true)]
+ public async Task IssuerValidatorAsyncTests(IssuerValidationResultsTheoryData theoryData)
+ {
+ CompareContext context = TestUtilities.WriteHeader($"{this}.IssuerValidatorAsyncTests", theoryData);
+
+ try
+ {
+ IssuerValidationResult issuerValidationResult = await Validators.ValidateIssuerAsync(
+ theoryData.Issuer,
+ theoryData.SecurityToken,
+ theoryData.ValidationParameters,
+ new CallContext(),
+ CancellationToken.None).ConfigureAwait(false);
+
+ theoryData.ExpectedException.ProcessException(issuerValidationResult.Exception, context);
+ IdentityComparer.AreIssuerValidationResultsEqual(
+ issuerValidationResult,
+ theoryData.IssuerValidationResult,
+ context);
+ }
+ catch (SecurityTokenInvalidIssuerException ex)
+ {
+ theoryData.ExpectedException.ProcessException(ex, context);
+ }
+
+ TestUtilities.AssertFailIfErrors(context);
+ }
+
+ public static TheoryData IssuerValdationResultsTestCases
+ {
+ get
+ {
+ TheoryData theoryData = new();
+
+ string validIssuer = Guid.NewGuid().ToString();
+ string issClaim = Guid.NewGuid().ToString();
+ theoryData.Add(new IssuerValidationResultsTheoryData("Invalid_Issuer")
+ {
+ ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX10205:"),
+ Issuer = issClaim,
+ IssuerValidationResult = new IssuerValidationResult(
+ issClaim,
+ ValidationFailureType.IssuerValidationFailed,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10205,
+ LogHelper.MarkAsNonPII(issClaim),
+ LogHelper.MarkAsNonPII(validIssuer),
+ LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(null)),
+ LogHelper.MarkAsNonPII(null)),
+ typeof(SecurityTokenInvalidIssuerException),
+ new StackFrame(true))),
+ IsValid = false,
+ SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, issClaim),
+ ValidationParameters = new TokenValidationParameters { ValidIssuer = validIssuer }
+ });
+
+ theoryData.Add(new IssuerValidationResultsTheoryData("NULL_Issuer")
+ {
+ ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX10211:"),
+ IssuerValidationResult = new IssuerValidationResult(
+ null,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10211,
+ LogHelper.MarkAsNonPII(null),
+ LogHelper.MarkAsNonPII(validIssuer),
+ LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(null)),
+ LogHelper.MarkAsNonPII(null)),
+ typeof(SecurityTokenInvalidIssuerException),
+ new StackFrame(true))),
+ IsValid = false,
+ SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, issClaim),
+ ValidationParameters = new TokenValidationParameters(),
+ });
+
+ return theoryData;
+ }
+ }
+ }
+
+ public class IssuerValidationResultsTheoryData : TheoryDataBase
+ {
+ public IssuerValidationResultsTheoryData(string testId) : base(testId)
+ {
+ }
+
+ public BaseConfiguration Configuration { get; set; }
+
+ public string Issuer { get; set; }
+
+ internal IssuerValidationResult IssuerValidationResult { get; set; }
+
+ public bool IsValid { get; set; }
+
+ public SecurityToken SecurityToken { get; set; }
+
+ public TokenValidationParameters ValidationParameters { get; set; }
+
+ internal ValidationFailureType ValidationFailureType { get; set; }
+ }
+}
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/ValidatorsTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidatorsTests.cs
similarity index 99%
rename from test/Microsoft.IdentityModel.Tokens.Tests/ValidatorsTests.cs
rename to test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidatorsTests.cs
index d7af19095e..181c05e926 100644
--- a/test/Microsoft.IdentityModel.Tokens.Tests/ValidatorsTests.cs
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidatorsTests.cs
@@ -8,8 +8,6 @@
using Microsoft.IdentityModel.TestUtils;
using Xunit;
-#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
-
namespace Microsoft.IdentityModel.Tokens.Tests
{
public class ValidatorsTests
@@ -510,5 +508,3 @@ public class AudienceValidationTheoryData : TheoryDataBase
}
}
}
-
-#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant