Skip to content

Commit

Permalink
Adding LKG feature into JwtSecurityTokenHandler
Browse files Browse the repository at this point in the history
Adding tests for SignatureValidatorUsingConfiguration

address comment

add IsRecoverableConfiguration

mark metadata address as non-PII
  • Loading branch information
mafurman authored and brentschmaltz committed Jan 23, 2022
1 parent b253983 commit 9b36f95
Show file tree
Hide file tree
Showing 8 changed files with 1,150 additions and 755 deletions.
29 changes: 7 additions & 22 deletions src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1021,37 +1021,22 @@ private TokenValidationResult ValidateToken(string token, JsonWebToken outerToke
{
if (tokenValidationResult.IsValid)
{
// Set current configuration as LKG if it exists and is not the same as the LKG.
if (currentConfiguration != null && currentConfiguration != validationParameters.ConfigurationManager.LastKnownGoodConfiguration)
// Set current configuration as LKG if it exists and has not already been set as the LKG.
if (currentConfiguration != null && !ReferenceEquals(currentConfiguration, validationParameters.ConfigurationManager.LastKnownGoodConfiguration))
validationParameters.ConfigurationManager.LastKnownGoodConfiguration = currentConfiguration;

return tokenValidationResult;
}
// using 'GetType()' instead of 'is' as SecurityTokenUnableToValidException (and others) extend SecurityTokenInvalidSignatureException
// we want to make sure that the clause for SecurityTokenUnableToValidateException is hit so that the ValidationFailure is checked
else if (tokenValidationResult.Exception.GetType().Equals(typeof(SecurityTokenInvalidSignatureException))
|| tokenValidationResult.Exception is SecurityTokenInvalidSigningKeyException
|| tokenValidationResult.Exception is SecurityTokenInvalidIssuerException
|| (tokenValidationResult.Exception is SecurityTokenUnableToValidateException
// we should not try to revalidate with the LKG or request a refresh if the token has an invalid lifetime
&& (tokenValidationResult.Exception as SecurityTokenUnableToValidateException).ValidationFailure != ValidationFailure.InvalidLifetime)
|| tokenValidationResult.Exception is SecurityTokenSignatureKeyNotFoundException)
else if (TokenUtilities.IsRecoverableException(tokenValidationResult.Exception))
{
if (validationParameters.ConfigurationManager.UseLastKnownGoodConfiguration
&& validationParameters.ConfigurationManager.LastKnownGoodConfiguration != null
&& validationParameters.ConfigurationManager.LastKnownGoodConfiguration != currentConfiguration)
if (TokenUtilities.IsRecoverableConfiguration(validationParameters, currentConfiguration, out currentConfiguration))
{
// Inform the user that the LKG is expired.
if (!validationParameters.ConfigurationManager.IsLastKnownGoodValid)
LogHelper.LogInformation(TokenLogMessages.IDX10263);
else
{
currentConfiguration = validationParameters.ConfigurationManager.LastKnownGoodConfiguration;
tokenValidationResult = decryptedJwt != null ? ValidateJWE(outerToken, decryptedJwt, validationParameters, currentConfiguration) : ValidateJWS(token, validationParameters, currentConfiguration); ;
tokenValidationResult = decryptedJwt != null ? ValidateJWE(outerToken, decryptedJwt, validationParameters, currentConfiguration) : ValidateJWS(token, validationParameters, currentConfiguration);

if (tokenValidationResult.IsValid)
return tokenValidationResult;
}
if (tokenValidationResult.IsValid)
return tokenValidationResult;
}

// If we were still unable to validate, attempt to refresh the configuration and validate using it
Expand Down
43 changes: 43 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,5 +204,48 @@ internal static IEnumerable<Claim> MergeClaims(IEnumerable<Claim> claims, IEnume

return result;
}

/// <summary>
/// Check whether the given exception type is recoverable by LKG.
/// </summary>
/// <param name="exception">The exception to check.</param>
/// <returns><c>true</c> if the exception is certain types of exceptions otherwise, <c>false</c>.</returns>
internal static bool IsRecoverableException(Exception exception)
{
// using 'GetType()' instead of 'is' as SecurityTokenUnableToValidException (and others) extend SecurityTokenInvalidSignatureException
// we want to make sure that the clause for SecurityTokenUnableToValidateException is hit so that the ValidationFailure is checked
return exception.GetType().Equals(typeof(SecurityTokenInvalidSignatureException))
|| exception is SecurityTokenInvalidSigningKeyException
|| exception is SecurityTokenInvalidIssuerException
// we should not try to revalidate with the LKG or request a refresh if the token has an invalid lifetime
|| (exception as SecurityTokenUnableToValidateException)?.ValidationFailure != ValidationFailure.InvalidLifetime
|| exception is SecurityTokenSignatureKeyNotFoundException;
}

/// <summary>
/// Check whether the given configuration is recoverable by LKG.
/// </summary>
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> to be used for validation.</param>
/// <param name="configuration">The <see cref="BaseConfiguration"/> to check.</param>
/// <param name="currentConfiguration">The updated <see cref="BaseConfiguration"/>.</param>
/// <returns><c>true</c> if the configuration is recoverable otherwise, <c>false</c>.</returns>
internal static bool IsRecoverableConfiguration(TokenValidationParameters validationParameters, BaseConfiguration configuration, out BaseConfiguration currentConfiguration)
{
bool isRecoverableConfiguration = (validationParameters.ConfigurationManager.UseLastKnownGoodConfiguration
&& validationParameters.ConfigurationManager.LastKnownGoodConfiguration != null
&& !ReferenceEquals(configuration, validationParameters.ConfigurationManager.LastKnownGoodConfiguration));

currentConfiguration = configuration;
if (isRecoverableConfiguration)
{
// Inform the user that the LKG is expired.
if (!validationParameters.ConfigurationManager.IsLastKnownGoodValid)
LogHelper.LogInformation(TokenLogMessages.IDX10263);
else
currentConfiguration = validationParameters.ConfigurationManager.LastKnownGoodConfiguration;
}

return isRecoverableConfiguration;
}
}
}
5 changes: 5 additions & 0 deletions src/System.IdentityModel.Tokens.Jwt/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@
[assembly: SuppressMessage("Design", "CA1054:Uri parameters should not be strings", Justification = "<Pending>", Scope = "member", Target = "~M:System.IdentityModel.Tokens.Jwt.JwtPayload.Base64UrlDeserialize(System.String)~System.IdentityModel.Tokens.Jwt.JwtPayload")]
[assembly: SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Holds no members", Scope = "type", Target = "~T:System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string", Scope = "member", Target = "~M:System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.GetContentEncryptionKeys(System.IdentityModel.Tokens.Jwt.JwtSecurityToken,Microsoft.IdentityModel.Tokens.TokenValidationParameters)~System.Collections.Generic.IEnumerable{Microsoft.IdentityModel.Tokens.SecurityKey}")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string", Scope = "member", Target = "~M:System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(System.String,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.Tokens.SecurityToken@)~System.Security.Claims.ClaimsPrincipal")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string.", Scope = "member", Target = "~M:System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(System.String,System.IdentityModel.Tokens.Jwt.JwtSecurityToken,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.Tokens.SecurityToken@)~System.Security.Claims.ClaimsPrincipal")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string.", Scope = "member", Target = "~M:System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(System.String,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.Tokens.BaseConfiguration)~System.IdentityModel.Tokens.Jwt.JwtSecurityToken")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "<Exception is returned from the method.>", Scope = "member", Target = "~M:System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateJWE(System.String,System.IdentityModel.Tokens.Jwt.JwtSecurityToken,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.Tokens.BaseConfiguration,Microsoft.IdentityModel.Tokens.SecurityToken@,System.Runtime.ExceptionServices.ExceptionDispatchInfo@)~System.Security.Claims.ClaimsPrincipal")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "<Exception is returned from the method.>", Scope = "member", Target = "~M:System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateJWS(System.String,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.Tokens.BaseConfiguration,Microsoft.IdentityModel.Tokens.SecurityToken@,System.Runtime.ExceptionServices.ExceptionDispatchInfo@)~System.Security.Claims.ClaimsPrincipal")]
Loading

0 comments on commit 9b36f95

Please sign in to comment.