Skip to content

Commit

Permalink
Added cancellationToken to ValidateTokenAsync flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Franco Fung committed May 29, 2024
1 parent b456053 commit 9280300
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 19 deletions.
71 changes: 52 additions & 19 deletions src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,28 @@ public virtual TokenValidationResult ValidateToken(string token, TokenValidation
/// <para><exception cref="SecurityTokenMalformedException">if the validationParameters.TokenReader delegate is not able to parse/read the token as a valid <see cref="JsonWebToken"/>, <see cref="ReadToken(string, TokenValidationParameters)"/></exception></para>
/// </remarks>
public override async Task<TokenValidationResult> ValidateTokenAsync(string token, TokenValidationParameters validationParameters)
{
return await ValidateTokenAsync(token, validationParameters, CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// 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.
/// </summary>
/// <param name="token">The token to be validated.</param>
/// <param name="validationParameters">A <see cref="TokenValidationParameters"/> required for validation.</param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
/// <returns>A <see cref="TokenValidationResult"/></returns>
/// <remarks>
/// <para>TokenValidationResult.Exception will be set to one of the following exceptions if the <paramref name="token"/> is invalid.</para>
/// <para><exception cref="ArgumentNullException">if <paramref name="token"/> is null or empty.</exception></para>
/// <para><exception cref="ArgumentNullException">if <paramref name="validationParameters"/> is null.</exception></para>
/// <para><exception cref="ArgumentException">'token.Length' is greater than <see cref="TokenHandler.MaximumTokenSizeInBytes"/>.</exception></para>
/// <para><exception cref="SecurityTokenMalformedException">if <paramref name="token"/> is not a valid <see cref="JsonWebToken"/>, <see cref="ReadToken(string, TokenValidationParameters)"/></exception></para>
/// <para><exception cref="SecurityTokenMalformedException">if the validationParameters.TokenReader delegate is not able to parse/read the token as a valid <see cref="JsonWebToken"/>, <see cref="ReadToken(string, TokenValidationParameters)"/></exception></para>
/// </remarks>
internal override async Task<TokenValidationResult> ValidateTokenAsync(string token, TokenValidationParameters validationParameters, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(token))
return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(token)), IsValid = false };
Expand All @@ -537,11 +559,12 @@ public override async Task<TokenValidationResult> ValidateTokenAsync(string toke
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 };

#pragma warning disable CA1031
try
{
TokenValidationResult result = ReadToken(token, validationParameters);
if (result.IsValid)
return await ValidateTokenAsync(result.SecurityToken, validationParameters).ConfigureAwait(false);
return await ValidateTokenAsync(result.SecurityToken, validationParameters, cancellationToken).ConfigureAwait(false);

return result;
}
Expand All @@ -553,10 +576,17 @@ public override async Task<TokenValidationResult> ValidateTokenAsync(string toke
IsValid = false
};
}
#pragma warning restore CA1031
}

/// <inheritdoc/>
public override async Task<TokenValidationResult> ValidateTokenAsync(SecurityToken token, TokenValidationParameters validationParameters)
{
return await ValidateTokenAsync(token, validationParameters, CancellationToken.None).ConfigureAwait(false);
}

/// <inheritdoc/>
internal override async Task<TokenValidationResult> ValidateTokenAsync(SecurityToken token, TokenValidationParameters validationParameters, CancellationToken cancellationToken)
{
if (token == null)
throw LogHelper.LogArgumentNullException(nameof(token));
Expand All @@ -570,8 +600,9 @@ public override async Task<TokenValidationResult> ValidateTokenAsync(SecurityTok

try
{
return await ValidateTokenAsync(jwt, validationParameters).ConfigureAwait(false);
return await ValidateTokenAsync(jwt, validationParameters, cancellationToken).ConfigureAwait(false);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
{
return new TokenValidationResult
Expand All @@ -580,6 +611,7 @@ public override async Task<TokenValidationResult> ValidateTokenAsync(SecurityTok
IsValid = false
};
}
#pragma warning restore CA1031 // Do not catch general exception types
}

/// <summary>
Expand Down Expand Up @@ -635,15 +667,16 @@ private static TokenValidationResult ReadToken(string token, TokenValidationPara
/// </summary>
/// <param name="jsonWebToken">The JWT token</param>
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> to be used for validation.</param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
/// <returns></returns>
private async ValueTask<TokenValidationResult> ValidateTokenAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters)
private async ValueTask<TokenValidationResult> ValidateTokenAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, CancellationToken cancellationToken)
{
BaseConfiguration currentConfiguration = null;
if (validationParameters.ConfigurationManager != null)
{
try
{
currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(cancellationToken).ConfigureAwait(false);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
Expand All @@ -656,7 +689,7 @@ private async ValueTask<TokenValidationResult> ValidateTokenAsync(JsonWebToken j
}
}

TokenValidationResult tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false);
TokenValidationResult tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, currentConfiguration, cancellationToken).ConfigureAwait(false);
if (validationParameters.ConfigurationManager != null)
{
if (tokenValidationResult.IsValid)
Expand All @@ -678,12 +711,12 @@ private async ValueTask<TokenValidationResult> ValidateTokenAsync(JsonWebToken j
validationParameters.ConfigurationManager.RequestRefresh();
validationParameters.RefreshBeforeValidation = true;
var lastConfig = currentConfiguration;
currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(cancellationToken).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);
tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, currentConfiguration, cancellationToken).ConfigureAwait(false);

if (tokenValidationResult.IsValid)
{
Expand All @@ -703,7 +736,7 @@ private async ValueTask<TokenValidationResult> ValidateTokenAsync(JsonWebToken j
{
if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(jsonWebToken.Kid, currentConfiguration, lkgConfiguration, recoverableException))
{
tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, lkgConfiguration).ConfigureAwait(false);
tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, lkgConfiguration, cancellationToken).ConfigureAwait(false);

if (tokenValidationResult.IsValid)
return tokenValidationResult;
Expand All @@ -716,14 +749,14 @@ private async ValueTask<TokenValidationResult> ValidateTokenAsync(JsonWebToken j
return tokenValidationResult;
}

private ValueTask<TokenValidationResult> ValidateTokenAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
private ValueTask<TokenValidationResult> ValidateTokenAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration, CancellationToken cancellationToken)
{
return jsonWebToken.IsEncrypted ?
ValidateJWEAsync(jsonWebToken, validationParameters, configuration) :
ValidateJWSAsync(jsonWebToken, validationParameters, configuration);
ValidateJWEAsync(jsonWebToken, validationParameters, configuration, cancellationToken) :
ValidateJWSAsync(jsonWebToken, validationParameters, configuration, cancellationToken);
}

private async ValueTask<TokenValidationResult> ValidateJWSAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
private async ValueTask<TokenValidationResult> ValidateJWSAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration, CancellationToken cancellationToken)
{
try
{
Expand All @@ -734,21 +767,21 @@ private async ValueTask<TokenValidationResult> ValidateJWSAsync(JsonWebToken jso
if (validationParameters.SignatureValidator != null || validationParameters.SignatureValidatorUsingConfiguration != null)
{
var validatedToken = ValidateSignatureUsingDelegates(jsonWebToken, validationParameters, configuration);
tokenValidationResult = await ValidateTokenPayloadAsync(validatedToken, validationParameters, configuration).ConfigureAwait(false);
tokenValidationResult = await ValidateTokenPayloadAsync(validatedToken, validationParameters, configuration, cancellationToken).ConfigureAwait(false);
Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, validatedToken, validationParameters, configuration);
}
else
{
if (validationParameters.ValidateSignatureLast)
{
tokenValidationResult = await ValidateTokenPayloadAsync(jsonWebToken, validationParameters, configuration).ConfigureAwait(false);
tokenValidationResult = await ValidateTokenPayloadAsync(jsonWebToken, validationParameters, configuration, cancellationToken).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);
tokenValidationResult = await ValidateTokenPayloadAsync(validatedToken, validationParameters, configuration, cancellationToken).ConfigureAwait(false);
}
}

Expand All @@ -767,15 +800,15 @@ private async ValueTask<TokenValidationResult> ValidateJWSAsync(JsonWebToken jso
}
}

private async ValueTask<TokenValidationResult> ValidateJWEAsync(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
private async ValueTask<TokenValidationResult> ValidateJWEAsync(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration, CancellationToken cancellationToken)
{
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);
tokenValidationResult = await ValidateJWSAsync(tokenValidationResult.SecurityToken as JsonWebToken, validationParameters, configuration, cancellationToken).ConfigureAwait(false);
if (!tokenValidationResult.IsValid)
return tokenValidationResult;

Expand Down Expand Up @@ -838,7 +871,7 @@ private static JsonWebToken ValidateSignatureAndIssuerSecurityKey(JsonWebToken j
return validatedToken;
}

private async ValueTask<TokenValidationResult> ValidateTokenPayloadAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
private async ValueTask<TokenValidationResult> ValidateTokenPayloadAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration, CancellationToken cancellationToken)
{
var expires = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Exp) ? (DateTime?)jsonWebToken.ValidTo : null;
var notBefore = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Nbf) ? (DateTime?)jsonWebToken.ValidFrom : null;
Expand All @@ -857,7 +890,7 @@ private async ValueTask<TokenValidationResult> ValidateTokenPayloadAsync(JsonWeb
// 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);
await ValidateTokenAsync(jsonWebToken.Actor, validationParameters.ActorValidationParameters ?? validationParameters, cancellationToken).ConfigureAwait(false);

if (!tokenValidationResult.IsValid)
return tokenValidationResult;
Expand Down
23 changes: 23 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/TokenHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using System.ComponentModel;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using static Microsoft.IdentityModel.Logging.LogHelper;

Expand Down Expand Up @@ -65,6 +66,17 @@ public int TokenLifetimeInMinutes
/// <returns>A <see cref="TokenValidationResult"/></returns>
public virtual Task<TokenValidationResult> ValidateTokenAsync(string token, TokenValidationParameters validationParameters) => throw new NotImplementedException();

/// <summary>
/// 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.
/// </summary>
/// <param name="token">The token to be validated.</param>
/// <param name="validationParameters">A <see cref="TokenValidationParameters"/> required for validation.</param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
/// <returns>A <see cref="TokenValidationResult"/></returns>
internal virtual Task<TokenValidationResult> ValidateTokenAsync(string token, TokenValidationParameters validationParameters, CancellationToken cancellationToken) => throw new NotImplementedException();

/// <summary>
/// Validates a token.
/// On a validation failure, no exception will be thrown; instead, the exception will be set in the returned TokenValidationResult.Exception property.
Expand All @@ -75,6 +87,17 @@ public int TokenLifetimeInMinutes
/// <returns>A <see cref="TokenValidationResult"/></returns>
public virtual Task<TokenValidationResult> ValidateTokenAsync(SecurityToken token, TokenValidationParameters validationParameters) => throw new NotImplementedException();

/// <summary>
/// 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.
/// </summary>
/// <param name="token">The <see cref="SecurityToken"/> to be validated.</param>
/// <param name="validationParameters">A <see cref="TokenValidationParameters"/> required for validation.</param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
/// <returns>A <see cref="TokenValidationResult"/></returns>
internal virtual Task<TokenValidationResult> ValidateTokenAsync(SecurityToken token, TokenValidationParameters validationParameters, CancellationToken cancellationToken) => throw new NotImplementedException();

/// <summary>
/// Converts a string into an instance of <see cref="SecurityToken"/>.
/// </summary>
Expand Down

0 comments on commit 9280300

Please sign in to comment.