diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs index 631e74584e..0742dbdd58 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs @@ -1325,7 +1325,8 @@ private async Task ValidateJWSAsync( return new TokenValidationResult { Exception = ex, - IsValid = false + IsValid = false, + TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jsonWebToken : null }; } } @@ -1363,7 +1364,8 @@ private async Task ValidateJWEAsync( return new TokenValidationResult { Exception = ex, - IsValid = false + IsValid = false, + TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jwtToken : null }; } } diff --git a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs index 2352e2db7c..5c882fd626 100644 --- a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs +++ b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs @@ -211,6 +211,7 @@ protected TokenValidationParameters(TokenValidationParameters other) ConfigurationManager = other.ConfigurationManager; CryptoProviderFactory = other.CryptoProviderFactory; DebugId = other.DebugId; + IncludeTokenOnFailedValidation = other.IncludeTokenOnFailedValidation; IgnoreTrailingSlashWhenValidatingAudience = other.IgnoreTrailingSlashWhenValidatingAudience; IssuerSigningKey = other.IssuerSigningKey; IssuerSigningKeyResolver = other.IssuerSigningKeyResolver; @@ -427,6 +428,10 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken, [DefaultValue(true)] public bool IgnoreTrailingSlashWhenValidatingAudience { get; set; } = true; + /// + /// Gets or sets the flag that indicates whether to include the when the validation fails. + /// + public bool IncludeTokenOnFailedValidation { get; set; } = false; /// /// Gets or sets a delegate for validating the that signed the token. diff --git a/src/Microsoft.IdentityModel.Tokens/TokenValidationResult.cs b/src/Microsoft.IdentityModel.Tokens/TokenValidationResult.cs index 7880018560..71f2c60978 100644 --- a/src/Microsoft.IdentityModel.Tokens/TokenValidationResult.cs +++ b/src/Microsoft.IdentityModel.Tokens/TokenValidationResult.cs @@ -142,6 +142,11 @@ public bool IsValid /// public SecurityToken SecurityToken { get; set; } + /// + /// The to be returned when validation fails. + /// + public SecurityToken TokenOnFailedValidation { get; internal set; } + /// /// Gets or sets the that contains call information. /// diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs index 458f2236b7..79d09a7f7a 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs @@ -3358,6 +3358,77 @@ public static TheoryData SecurityKeyNotFoundExceptionTest }, }; } + + [Theory, MemberData(nameof(IncludeSecurityTokenOnFailureTestTheoryData))] + public void IncludeSecurityTokenOnFailedValidationTest(CreateTokenTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.IncludeSecurityTokenOnFailedValidationTest", theoryData); + + try + { + var handler = new JsonWebTokenHandler(); + var token = handler.CreateToken(theoryData.TokenDescriptor); + var validationResult = handler.ValidateToken(token, theoryData.ValidationParameters); + if (theoryData.ValidationParameters.IncludeTokenOnFailedValidation) + { + Assert.NotNull(validationResult.TokenOnFailedValidation); + } + else + { + Assert.Null(validationResult.TokenOnFailedValidation); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData IncludeSecurityTokenOnFailureTestTheoryData() + { + return new TheoryData() + { + new CreateTokenTheoryData + { + First = true, + TestId = "TokenExpiredIncludeTokenOnFailedValidation", + TokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(Default.PayloadClaimsExpired), + Expires = DateTime.UtcNow.Subtract(new TimeSpan(0, 10, 0)), + IssuedAt = DateTime.UtcNow.Subtract(new TimeSpan(1, 0, 0)), + NotBefore = DateTime.UtcNow.Subtract(new TimeSpan(1, 0, 0)), + SigningCredentials = Default.AsymmetricSigningCredentials, + }, + ValidationParameters = new TokenValidationParameters + { + IssuerSigningKey = Default.SymmetricSigningKey, + ValidIssuer = Default.Issuer, + IncludeTokenOnFailedValidation = true + } + }, + new CreateTokenTheoryData + { + First = true, + TestId = "TokenExpiredNotIncludeTokenOnFailedValidation", + TokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(Default.PayloadClaimsExpired), + Expires = DateTime.UtcNow.Subtract(new TimeSpan(0, 10, 0)), + IssuedAt = DateTime.UtcNow.Subtract(new TimeSpan(1, 0, 0)), + NotBefore = DateTime.UtcNow.Subtract(new TimeSpan(1, 0, 0)), + SigningCredentials = Default.AsymmetricSigningCredentials, + }, + ValidationParameters = new TokenValidationParameters + { + IssuerSigningKey = Default.SymmetricSigningKey, + ValidIssuer = Default.Issuer, + } + }, + }; + } } public class CreateTokenTheoryData : TheoryDataBase diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs index 2ce625a6ff..358ffc9f2f 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs @@ -16,14 +16,16 @@ namespace Microsoft.IdentityModel.Tokens.Tests { public class TokenValidationParametersTests { + int ExpectedPropertyCount = 57; + [Fact] public void Publics() { TokenValidationParameters validationParameters = new TokenValidationParameters(); Type type = typeof(TokenValidationParameters); PropertyInfo[] properties = type.GetProperties(); - if (properties.Length != 56) - Assert.True(false, "Number of properties has changed from 56 to: " + properties.Length + ", adjust tests"); + if (properties.Length != ExpectedPropertyCount) + Assert.True(false, $"Number of properties has changed from {ExpectedPropertyCount} to: " + properties.Length + ", adjust tests"); TokenValidationParameters actorValidationParameters = new TokenValidationParameters(); SecurityKey issuerSigningKey = KeyingMaterial.DefaultX509Key_2048_Public; @@ -157,8 +159,8 @@ public void GetSets() TokenValidationParameters validationParameters = new TokenValidationParameters(); Type type = typeof(TokenValidationParameters); PropertyInfo[] properties = type.GetProperties(); - if (properties.Length != 56) - Assert.True(false, "Number of public fields has changed from 56 to: " + properties.Length + ", adjust tests"); + if (properties.Length != ExpectedPropertyCount) + Assert.True(false, $"Number of public fields has changed from {ExpectedPropertyCount} to: " + properties.Length + ", adjust tests"); GetSetContext context = new GetSetContext diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationResultTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationResultTests.cs index 4aa55ced7e..2fa76c11af 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationResultTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationResultTests.cs @@ -21,8 +21,8 @@ public void GetSets() TokenValidationResult tokenValidationResult = new TokenValidationResult(); Type type = typeof(TokenValidationResult); PropertyInfo[] properties = type.GetProperties(); - if (properties.Length != 9) - Assert.True(false, "Number of public fields has changed from 9 to: " + properties.Length + ", adjust tests"); + if (properties.Length != 10) + Assert.True(false, "Number of public fields has changed from 10 to: " + properties.Length + ", adjust tests"); GetSetContext context = new GetSetContext