diff --git a/src/Microsoft.IdentityModel.Tokens/Validators.cs b/src/Microsoft.IdentityModel.Tokens/Validators.cs
index 38364489ad..20e76450e7 100644
--- a/src/Microsoft.IdentityModel.Tokens/Validators.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Validators.cs
@@ -373,6 +373,16 @@ internal static void ValidateIssuerSecurityKey(SecurityKey securityKey, Security
if (securityToken == null)
throw LogHelper.LogArgumentNullException(nameof(securityToken));
+ ValidateIssuerSigningKeyLifeTime(securityKey, validationParameters);
+ }
+
+ ///
+ /// Given a signing key, when it's derived from a certificate, validates that the certificate is already active and non-expired
+ ///
+ /// The that signed the .
+ /// The that are used to validate the token.
+ internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, TokenValidationParameters validationParameters)
+ {
X509SecurityKey x509SecurityKey = securityKey as X509SecurityKey;
if (x509SecurityKey?.Certificate is X509Certificate2 cert)
{
diff --git a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs
index 1ce12b0c1f..83090efa27 100644
--- a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs
+++ b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs
@@ -24,7 +24,7 @@ public class AadIssuerValidator
private static readonly TimeSpan LastKnownGoodConfigurationLifetime = new TimeSpan(0, 24, 0, 0);
internal const string V2EndpointSuffix = "/v2.0";
- internal const string TenantidTemplate = "{tenantid}";
+ internal const string TenantIdTemplate = "{tenantid}";
internal AadIssuerValidator(
HttpClient httpClient,
@@ -292,9 +292,9 @@ private static bool IsValidIssuer(string validIssuerTemplate, string tenantId, s
if (string.IsNullOrEmpty(validIssuerTemplate))
return false;
- if (validIssuerTemplate.Contains(TenantidTemplate))
+ if (validIssuerTemplate.Contains(TenantIdTemplate))
{
- return validIssuerTemplate.Replace(TenantidTemplate, tenantId) == actualIssuer;
+ return validIssuerTemplate.Replace(TenantIdTemplate, tenantId) == actualIssuer;
}
else
{
@@ -311,7 +311,7 @@ private BaseConfigurationManager GetEffectiveConfigurationManager(SecurityToken
/// A JWT token.
/// A string containing the tenant ID, if found or .
/// Only and are acceptable types.
- private static string GetTenantIdFromToken(SecurityToken securityToken)
+ internal static string GetTenantIdFromToken(SecurityToken securityToken)
{
if (securityToken is JwtSecurityToken jwtSecurityToken)
{
diff --git a/src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs b/src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs
new file mode 100644
index 0000000000..d745bf124c
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs
@@ -0,0 +1,133 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Linq;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Protocols.OpenIdConnect;
+using Microsoft.IdentityModel.Tokens;
+
+namespace Microsoft.IdentityModel.Validators
+{
+ ///
+ /// A generic class for additional validation checks on issued by the Microsoft identity platform (AAD).
+ ///
+ public static class AadTokenValidationParametersExtension
+ {
+ ///
+ /// Enables the validation of the issuer of the signing keys used by the Microsoft identity platform (AAD) against the issuer of the token.
+ ///
+ /// The that are used to validate the token.
+ public static void EnableAadSigningKeyIssuerValidation(this TokenValidationParameters tokenValidationParameters)
+ {
+ if (tokenValidationParameters == null)
+ throw LogHelper.LogArgumentNullException(nameof(tokenValidationParameters));
+
+ var userProvidedIssuerSigningKeyValidatorUsingConfiguration = tokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration;
+ var userProvidedIssuerSigningKeyValidator = tokenValidationParameters.IssuerSigningKeyValidator;
+
+ tokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration = (securityKey, securityToken, tvp, config) =>
+ {
+ ValidateIssuerSigningKey(securityKey, securityToken, config);
+
+ // preserve and run provided logic
+ if (userProvidedIssuerSigningKeyValidatorUsingConfiguration != null)
+ return userProvidedIssuerSigningKeyValidatorUsingConfiguration(securityKey, securityToken, tvp, config);
+
+ if (userProvidedIssuerSigningKeyValidator != null)
+ return userProvidedIssuerSigningKeyValidator(securityKey, securityToken, tvp);
+
+ return ValidateIssuerSigningKeyCertificate(securityKey, tvp);
+ };
+ }
+
+ ///
+ /// Validates the issuer signing key.
+ ///
+ /// The that signed the .
+ /// The being validated, could be a JwtSecurityToken or JsonWebToken.
+ /// The provided.
+ /// true if the issuer signing key is valid; otherwise, false.
+ internal static bool ValidateIssuerSigningKey(SecurityKey securityKey, SecurityToken securityToken, BaseConfiguration configuration)
+ {
+ if (securityKey == null)
+ return true;
+
+ if (securityToken == null)
+ throw LogHelper.LogArgumentNullException(nameof(securityToken));
+
+ var openIdConnectConfiguration = configuration as OpenIdConnectConfiguration;
+ if (openIdConnectConfiguration == null)
+ return true;
+
+ var matchedKeyFromConfig = openIdConnectConfiguration.JsonWebKeySet?.Keys.FirstOrDefault(x => x != null && x.Kid == securityKey.KeyId);
+ if (matchedKeyFromConfig != null && matchedKeyFromConfig.AdditionalData.TryGetValue(OpenIdProviderMetadataNames.Issuer, out object value))
+ {
+ var signingKeyIssuer = value as string;
+ if (string.IsNullOrWhiteSpace(signingKeyIssuer))
+ return true;
+
+ var tenantIdFromToken = AadIssuerValidator.GetTenantIdFromToken(securityToken);
+ if (string.IsNullOrEmpty(tenantIdFromToken))
+ return true;
+
+ var tokenIssuer = securityToken.Issuer;
+
+#if NET6_0_OR_GREATER
+ if (!string.IsNullOrEmpty(tokenIssuer) && !tokenIssuer.Contains(tenantIdFromToken, StringComparison.Ordinal))
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX40004, LogHelper.MarkAsNonPII(tokenIssuer), LogHelper.MarkAsNonPII(tenantIdFromToken))));
+
+ // creating an effectiveSigningKeyIssuer is required as signingKeyIssuer might contain {tenantid}
+ var effectiveSigningKeyIssuer = signingKeyIssuer.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken, StringComparison.Ordinal);
+ var v2TokenIssuer = openIdConnectConfiguration.Issuer?.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken, StringComparison.Ordinal);
+#else
+ if (!string.IsNullOrEmpty(tokenIssuer) && !tokenIssuer.Contains(tenantIdFromToken))
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX40004, LogHelper.MarkAsNonPII(tokenIssuer), LogHelper.MarkAsNonPII(tenantIdFromToken))));
+
+ // creating an effectiveSigningKeyIssuer is required as signingKeyIssuer might contain {tenantid}
+ var effectiveSigningKeyIssuer = signingKeyIssuer.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken);
+ var v2TokenIssuer = openIdConnectConfiguration.Issuer?.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken);
+#endif
+
+ // comparing effectiveSigningKeyIssuer with v2TokenIssuer is required as well because of the following scenario:
+ // 1. service trusts /common/v2.0 endpoint
+ // 2. service receieves a v1 token that has issuer like sts.windows.net
+ // 3. signing key issuers will never match sts.windows.net as v1 endpoint doesn't have issuers attached to keys
+ // v2TokenIssuer is the representation of Token.Issuer (if it was a v2 issuer)
+ if (effectiveSigningKeyIssuer != tokenIssuer && effectiveSigningKeyIssuer != v2TokenIssuer)
+ throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX40005, LogHelper.MarkAsNonPII(tokenIssuer), LogHelper.MarkAsNonPII(effectiveSigningKeyIssuer))));
+ }
+
+ return true;
+ }
+
+ ///
+ /// Validates the issuer signing key certificate.
+ ///
+ /// The that signed the .
+ /// The that are used to validate the token.
+ /// true if the issuer signing key certificate is valid; otherwise, false.
+ internal static bool ValidateIssuerSigningKeyCertificate(SecurityKey securityKey, TokenValidationParameters validationParameters)
+ {
+ if (!validationParameters.RequireSignedTokens && securityKey == null)
+ {
+ LogHelper.LogInformation(Tokens.LogMessages.IDX10252);
+ return true;
+ }
+ else if (securityKey == null)
+ {
+ throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(securityKey), LogMessages.IDX40007));
+ }
+
+ if (!validationParameters.ValidateIssuerSigningKey)
+ {
+ LogHelper.LogVerbose(Tokens.LogMessages.IDX10237);
+ return true;
+ }
+
+ Tokens.Validators.ValidateIssuerSigningKeyLifeTime(securityKey, validationParameters);
+
+ return true;
+ }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/LogMessages.cs b/src/Microsoft.IdentityModel.Validators/LogMessages.cs
similarity index 68%
rename from src/Microsoft.IdentityModel.Validators/AadIssuerValidator/LogMessages.cs
rename to src/Microsoft.IdentityModel.Validators/LogMessages.cs
index 25cbc2e3fb..e8edf46300 100644
--- a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/LogMessages.cs
+++ b/src/Microsoft.IdentityModel.Validators/LogMessages.cs
@@ -20,5 +20,8 @@ internal static class LogMessages
// Protocol
public const string IDX40003 = "IDX40003: Neither `tid` nor `tenantId` claim is present in the token obtained from Microsoft identity platform. ";
+ public const string IDX40004 = "IDX40004: Token issuer: '{0}', does not contain the `tid` or `tenantId` claim present in the token: '{1}'.";
+ public const string IDX40005 = "IDX40005: Token issuer: '{0}', does not match the signing key issuer: '{1}'.";
+ public const string IDX40007 = "IDX40007: RequireSignedTokens property on ValidationParameters is set to true, but the issuer signing key is null.";
}
}
diff --git a/src/Microsoft.IdentityModel.Validators/Microsoft.IdentityModel.Validators.csproj b/src/Microsoft.IdentityModel.Validators/Microsoft.IdentityModel.Validators.csproj
index fe8b6863fc..d784708d0a 100644
--- a/src/Microsoft.IdentityModel.Validators/Microsoft.IdentityModel.Validators.csproj
+++ b/src/Microsoft.IdentityModel.Validators/Microsoft.IdentityModel.Validators.csproj
@@ -25,6 +25,7 @@
+
diff --git a/test/Microsoft.IdentityModel.Validators.Tests/AadSigningKeyIssuerValidatorTests.cs b/test/Microsoft.IdentityModel.Validators.Tests/AadSigningKeyIssuerValidatorTests.cs
new file mode 100644
index 0000000000..e768a2372f
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Validators.Tests/AadSigningKeyIssuerValidatorTests.cs
@@ -0,0 +1,353 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.IdentityModel.JsonWebTokens;
+using Microsoft.IdentityModel.Protocols;
+using Microsoft.IdentityModel.Protocols.OpenIdConnect;
+using Microsoft.IdentityModel.TestUtils;
+using Microsoft.IdentityModel.Tokens;
+using Xunit;
+
+#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
+
+namespace Microsoft.IdentityModel.Validators.Tests
+{
+ public class AadSigningKeyIssuerValidatorTests
+ {
+ [Theory, MemberData(nameof(EnableAadSigningKeyIssuerValidationTestCases))]
+ public async Task EnableAadSigningKeyIssuerValidationTests(AadSigningKeyIssuerTheoryData theoryData)
+ {
+ var context = TestUtilities.WriteHeader($"{this}.EnableAadSigningKeyIssuerValidationTests", theoryData);
+ try
+ {
+ // set delegates
+ bool delegateSet = false;
+ if (theoryData.SetDelegateUsingConfig)
+ {
+ theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration = (securityKey, securityToken, tvp, config) => { delegateSet = true; return true; };
+ }
+ else if (theoryData.SetDelegateWithoutConfig)
+ {
+ theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration = null;
+ theoryData.TokenValidationParameters.IssuerSigningKeyValidator = (securityKey, securityToken, tvp) => { delegateSet = true; return true; };
+ }
+
+ var handler = new JsonWebTokenHandler();
+ var jwt = handler.ReadJsonWebToken(Default.AsymmetricJws);
+ AadIssuerValidator.GetAadIssuerValidator(Default.AadV1Authority).ConfigurationManagerV1 = theoryData.TokenValidationParameters.ConfigurationManager;
+ theoryData.TokenValidationParameters.EnableAadSigningKeyIssuerValidation();
+
+ var validationResult = await handler.ValidateTokenAsync(jwt, theoryData.TokenValidationParameters).ConfigureAwait(false);
+ theoryData.ExpectedException.ProcessNoException(context);
+ Assert.NotNull(theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration);
+ Assert.True(validationResult.IsValid);
+
+ // verify delegates were executed
+ if (theoryData.SetDelegateUsingConfig || theoryData.SetDelegateWithoutConfig)
+ Assert.True(delegateSet);
+ }
+ catch (Exception ex)
+ {
+ theoryData.ExpectedException.ProcessException(ex, context);
+ }
+
+ TestUtilities.AssertFailIfErrors(context);
+ }
+
+ public static TheoryData EnableAadSigningKeyIssuerValidationTestCases()
+ {
+ var signingKeysConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer };
+ signingKeysConfig.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048);
+ var validationParameters = new TokenValidationParameters()
+ {
+ ConfigurationManager = new StaticConfigurationManager(signingKeysConfig),
+ ValidateIssuerSigningKey = true,
+ ValidateAudience = false,
+ ValidateLifetime = false
+ };
+
+ var theoryData = new TheoryData
+ {
+ new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "IssuerSigningKeyValidatorUsingConfiguration_Delegate_IsSetByWilson",
+ TokenValidationParameters = validationParameters
+ },
+ new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "IssuerSigningKeyValidatorUsingConfiguration_Delegate_IsSetByDeveloper",
+ TokenValidationParameters = validationParameters,
+ SetDelegateUsingConfig = true,
+ },
+ new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "IssuerSigningKeyValidator_Delegate_IsSetByDeveloper",
+ TokenValidationParameters = validationParameters,
+ SetDelegateWithoutConfig = true,
+ }
+ };
+
+ return theoryData;
+ }
+
+ [Theory, MemberData(nameof(ValidateIssuerSigningKeyCertificateTestCases))]
+ public void ValidateIssuerSigningKeyCertificateTests(AadSigningKeyIssuerTheoryData theoryData)
+ {
+ var context = TestUtilities.WriteHeader($"{this}.ValidateIssuerSigningKeyCertificateTests", theoryData);
+
+ try
+ {
+ var result = AadTokenValidationParametersExtension.ValidateIssuerSigningKeyCertificate(theoryData.SecurityKey, theoryData.TokenValidationParameters);
+ theoryData.ExpectedException.ProcessNoException(context);
+ Assert.True(result);
+ }
+ catch (Exception ex)
+ {
+ theoryData.ExpectedException.ProcessException(ex, context);
+ }
+
+ TestUtilities.AssertFailIfErrors(context);
+ }
+
+ public static TheoryData ValidateIssuerSigningKeyCertificateTestCases()
+ {
+ var theoryData = new TheoryData
+ {
+ new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "SecurityKeyIsNull",
+ SecurityKey = null,
+ TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = true, ValidateIssuerSigningKey = true },
+ ExpectedException = ExpectedException.ArgumentNullException("IDX40007:")
+ },
+ new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "SecurityKeyIsNull_RequireSignedTokensFalse",
+ SecurityKey = null,
+ TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = false, ValidateIssuerSigningKey = true },
+ },
+ new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "ServiceAcceptsUnsignedTokens",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = false, ValidateIssuerSigningKey = true },
+ },
+ new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "SkipValidaingIssuerSigningKey",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = true, ValidateIssuerSigningKey = false },
+ },
+ new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "SkipValidaingIssuerSigningKey",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = false, ValidateIssuerSigningKey = false },
+ },
+ new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "CertificateLifeTimeValidated",
+ SecurityKey = KeyingMaterial.X509SecurityKeySelfSigned1024_SHA256,
+ TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = true, ValidateIssuerSigningKey = true },
+ }
+ };
+
+ return theoryData;
+ }
+
+ [Theory, MemberData(nameof(ValidateIssuerSigningKeyTestCases))]
+ public void ValidateIssuerSigningKeyTests(AadSigningKeyIssuerTheoryData theoryData)
+ {
+ var context = TestUtilities.WriteHeader($"{this}.ValidateIssuerSigningKeyTests", theoryData);
+
+ try
+ {
+ var result = AadTokenValidationParametersExtension.ValidateIssuerSigningKey(theoryData.SecurityKey, theoryData.SecurityToken, theoryData.OpenIdConnectConfiguration);
+ theoryData.ExpectedException.ProcessNoException(context);
+ Assert.True(result);
+ }
+ catch (Exception ex)
+ {
+ theoryData.ExpectedException.ProcessException(ex, context);
+ }
+
+ TestUtilities.AssertFailIfErrors(context);
+ }
+
+ public static TheoryData ValidateIssuerSigningKeyTestCases
+ {
+ get
+ {
+ var theoryData = new TheoryData();
+
+ var tidClaim = new Claim(ValidatorConstants.ClaimNameTid, ValidatorConstants.TenantIdAsGuid);
+ var issClaim = new Claim(ValidatorConstants.ClaimNameIss, ValidatorConstants.AadIssuer);
+ var jwtSecurityToken = new JwtSecurityToken(issuer: ValidatorConstants.AadIssuer, claims: new[] { issClaim, tidClaim });
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "NullSecurityKey",
+ SecurityKey = null,
+ SecurityToken = jwtSecurityToken,
+ OpenIdConnectConfiguration = GetConfigurationMock()
+ });
+
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "NullSecurityToken",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ SecurityToken = null,
+ OpenIdConnectConfiguration = GetConfigurationMock(),
+ ExpectedException = ExpectedException.ArgumentNullException("IDX10000")
+ });
+
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "NullConfiguration",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ SecurityToken = jwtSecurityToken,
+ OpenIdConnectConfiguration = null,
+ });
+
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "NoSigningKeysInConfiguration",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ SecurityToken = jwtSecurityToken,
+ OpenIdConnectConfiguration = new OpenIdConnectConfiguration()
+ });
+
+ var mockConfiguration = GetConfigurationMock();
+ mockConfiguration.JsonWebKeySet.Keys.Add(KeyingMaterial.JsonWebKeyP384);
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "NoMatchingKeysInConfiguration",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ SecurityToken = jwtSecurityToken,
+ OpenIdConnectConfiguration = new OpenIdConnectConfiguration()
+ });
+
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "MissingIssuerInConfiguration",
+ SecurityKey = KeyingMaterial.JsonWebKeyP384,
+ SecurityToken = jwtSecurityToken,
+ OpenIdConnectConfiguration = mockConfiguration
+ });
+
+ var jwk = KeyingMaterial.JsonWebKeySymmetric128;
+ jwk.AdditionalData.Add(OpenIdProviderMetadataNames.Issuer, " ");
+ mockConfiguration.JsonWebKeySet.Keys.Add(jwk);
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "WhitespaceForIssuerInConfiguration",
+ SecurityKey = KeyingMaterial.JsonWebKeySymmetric128,
+ SecurityToken = jwtSecurityToken,
+ OpenIdConnectConfiguration = mockConfiguration,
+ });
+
+ jwk = KeyingMaterial.JsonWebKeyP521;
+ jwk.AdditionalData.Add(OpenIdProviderMetadataNames.Issuer, ValidatorConstants.UsGovIssuer);
+ mockConfiguration.JsonWebKeySet.Keys.Add(jwk);
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "JST_TokenIssuer_MismatchesWith_SigningKeyIssuer",
+ SecurityKey = KeyingMaterial.JsonWebKeyP521,
+ SecurityToken = jwtSecurityToken,
+ OpenIdConnectConfiguration = mockConfiguration,
+ ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40005")
+ });
+
+ List claims = new List
+ {
+ tidClaim,
+ issClaim
+ };
+ var jsonWebToken = new JsonWebToken(Default.Jwt(Default.SecurityTokenDescriptor(Default.SymmetricSigningCredentials, claims)));
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "JWT_TokenIssuer_MismatchesWith_SigningKeyIssuer",
+ SecurityKey = KeyingMaterial.JsonWebKeyP521,
+ SecurityToken = jsonWebToken,
+ OpenIdConnectConfiguration = mockConfiguration,
+ ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40005")
+ });
+
+ jwk = KeyingMaterial.JsonWebKeyP256;
+ jwk.AdditionalData.Add(OpenIdProviderMetadataNames.Issuer, ValidatorConstants.AadIssuerV2CommonAuthority);
+ mockConfiguration.JsonWebKeySet.Keys.Add(jwk);
+ mockConfiguration.Issuer = ValidatorConstants.AadIssuerV2CommonAuthority;
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "HappyPath_TokenIssuer_Matches_SigningKeyIssuer",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ SecurityToken = jwtSecurityToken,
+ OpenIdConnectConfiguration = mockConfiguration
+ });
+
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "MissingTenantIdClaimInToken",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ SecurityToken = new JwtSecurityToken(),
+ OpenIdConnectConfiguration = mockConfiguration
+ });
+
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "JST_TokenIssuer_MismatchesWith_TenantIdInToken",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ SecurityToken = new JwtSecurityToken(issuer: ValidatorConstants.AadIssuer, claims: new[] { issClaim, new Claim(ValidatorConstants.ClaimNameTid, ValidatorConstants.B2CTenantAsGuid) }),
+ OpenIdConnectConfiguration = mockConfiguration,
+ ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40004")
+ });
+
+ claims = new List
+ {
+ new Claim(ValidatorConstants.ClaimNameTid, ValidatorConstants.B2CTenantAsGuid),
+ issClaim
+ };
+ jsonWebToken = new JsonWebToken(Default.Jwt(Default.SecurityTokenDescriptor(Default.SymmetricSigningCredentials, claims)));
+ theoryData.Add(new AadSigningKeyIssuerTheoryData
+ {
+ TestId = "JWT_TokenIssuer_MismatchesWith_TenantIdInToken",
+ SecurityKey = KeyingMaterial.JsonWebKeyP256,
+ SecurityToken = jsonWebToken,
+ OpenIdConnectConfiguration = mockConfiguration,
+ ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40004")
+ });
+
+ return theoryData;
+ }
+ }
+
+ private static OpenIdConnectConfiguration GetConfigurationMock()
+ {
+ var config = new OpenIdConnectConfiguration();
+ config.JsonWebKeySet = new JsonWebKeySet();
+ config.JsonWebKeySet.Keys.Add(KeyingMaterial.JsonWebKeyP384);
+ return config;
+ }
+
+ public class AadSigningKeyIssuerTheoryData : TheoryDataBase
+ {
+ public SecurityKey SecurityKey { get; set; }
+
+ public TokenValidationParameters TokenValidationParameters { get; set; }
+
+ public SecurityToken SecurityToken { get; set; }
+
+ public OpenIdConnectConfiguration OpenIdConnectConfiguration { get; set; }
+
+ public bool SetDelegateUsingConfig { get; set; } = false;
+
+ public bool SetDelegateWithoutConfig { get; set; } = false;
+ }
+ }
+}
+
+#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant
diff --git a/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs b/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs
index 1c41fcd8d1..68adf2b3c0 100644
--- a/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs
+++ b/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs
@@ -31,6 +31,7 @@ internal class ValidatorConstants
public const string UsGovIssuer = "https://login.microsoftonline.us/" + UsGovTenantId + "/v2.0";
public const string UsGovTenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
public const string V1Issuer = "https://sts.windows.net/f645ad92-e38d-4d1a-b510-d1b09a74a8ca/";
+ public const string AadIssuerV2CommonAuthority = AadInstance + "/{tenantid}/v2.0";
public const string B2CSignUpSignInUserFlow = "b2c_1_susi";
public const string B2CInstance = "https://fabrikamb2c.b2clogin.com";