Skip to content

Commit

Permalink
modified token validation to be async throughout the call graph
Browse files Browse the repository at this point in the history
  • Loading branch information
Brent Schmaltz authored and brentschmaltz committed May 10, 2023
1 parent 25545cd commit 2468413
Show file tree
Hide file tree
Showing 12 changed files with 437 additions and 89 deletions.
41 changes: 21 additions & 20 deletions src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1298,7 +1298,7 @@ private async Task<TokenValidationResult> ValidateTokenAsync(JsonWebToken jsonWe
}
}

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

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

if (tokenValidationResult.IsValid)
return tokenValidationResult;
Expand All @@ -1358,15 +1358,15 @@ private async Task<TokenValidationResult> ValidateTokenAsync(JsonWebToken jsonWe
return tokenValidationResult;
}

private TokenValidationResult ValidateToken(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
private async Task<TokenValidationResult> ValidateTokenAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
{
if (jsonWebToken.IsEncrypted)
return ValidateJWE(jsonWebToken, validationParameters, configuration);
return await ValidateJWEAsync(jsonWebToken, validationParameters, configuration).ConfigureAwait(false);

return ValidateJWS(jsonWebToken, validationParameters, configuration);
return await ValidateJWSAsync(jsonWebToken, validationParameters, configuration).ConfigureAwait(false);
}

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

Expand All @@ -1410,16 +1410,15 @@ private TokenValidationResult ValidateJWS(JsonWebToken jsonWebToken, TokenValida
}
}

private TokenValidationResult ValidateJWE(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
private async Task<TokenValidationResult> ValidateJWEAsync(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
{
try
{
string jws = DecryptToken(jwtToken, validationParameters, configuration);
TokenValidationResult readTokenResult = ReadToken(jws, validationParameters);
if (!readTokenResult.IsValid)
return readTokenResult;
TokenValidationResult tokenValidationResult = ReadToken(DecryptToken(jwtToken, validationParameters, configuration), validationParameters);
if (!tokenValidationResult.IsValid)
return tokenValidationResult;

TokenValidationResult tokenValidationResult = ValidateJWS(readTokenResult.SecurityToken as JsonWebToken, validationParameters, configuration);
tokenValidationResult = await ValidateJWSAsync(tokenValidationResult.SecurityToken as JsonWebToken, validationParameters, configuration).ConfigureAwait(false);
if (!tokenValidationResult.IsValid)
return tokenValidationResult;

Expand Down Expand Up @@ -1482,14 +1481,14 @@ private static JsonWebToken ValidateSignatureAndIssuerSecurityKey(JsonWebToken j
return validatedToken;
}

private TokenValidationResult ValidateTokenPayload(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
private async Task<TokenValidationResult> 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 = Validators.ValidateIssuer(jsonWebToken.Issuer, jsonWebToken, validationParameters, configuration);
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))
Expand All @@ -1500,7 +1499,9 @@ private TokenValidationResult ValidateTokenPayload(JsonWebToken jsonWebToken, To
// 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 = ValidateToken(jsonWebToken.Actor, validationParameters.ActorValidationParameters ?? validationParameters);
TokenValidationResult tokenValidationResult =
await ValidateTokenAsync(jsonWebToken.Actor, validationParameters.ActorValidationParameters ?? validationParameters).ConfigureAwait(false);

if (!tokenValidationResult.IsValid)
return tokenValidationResult;
}
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,10 @@
[assembly: InternalsVisibleTo("Microsoft.IdentityModel.Validators, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("Microsoft.IdentityModel.Validators.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("Microsoft.IdentityModel.TestExtensions, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("Microsoft.IdentityModel.S2S, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("Microsoft.IdentityModel.S2S.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("Microsoft.IdentityModel.S2S.Configuration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("Microsoft.IdentityModel.S2S.Configuration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("Microsoft.IdentityModel.S2S.Extensions.AspNetCore.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("Microsoft.IdentityModel.S2S.Tokens, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("Microsoft.IdentityModel.S2S.Tokens.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
35 changes: 32 additions & 3 deletions src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Logging;

namespace Microsoft.IdentityModel.Tokens
Expand Down Expand Up @@ -100,6 +101,18 @@ namespace Microsoft.IdentityModel.Tokens
/// </remarks>
public delegate string IssuerValidatorUsingConfiguration(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration configuration);

/// <summary>
/// Definition for IssuerValidatorAsync. Left internal for now while we work out the details of async validation for all delegates.
/// </summary>
/// <param name="issuer">The issuer to validate.</param>
/// <param name="securityToken">The <see cref="SecurityToken"/> that is being validated.</param>
/// <param name="validationParameters"><see cref="TokenValidationParameters"/> required for validation.</param>
/// <returns>The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity".</returns>
/// <remarks>The delegate should return a non null string that represents the 'issuer'. If null a default value will be used.
/// <see cref="IssuerValidatorAsync"/> if set, will be called before <see cref="IssuerSigningKeyValidatorUsingConfiguration"/> or <see cref="IssuerSigningKeyValidator"/>
/// </remarks>
internal delegate Task<string> IssuerValidatorAsync(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters);

/// <summary>
/// Definition for LifetimeValidator.
/// </summary>
Expand Down Expand Up @@ -178,8 +191,9 @@ public class TokenValidationParameters
private string _roleClaimType = ClaimsIdentity.DefaultRoleClaimType;

/// <summary>
/// This is the fallback authenticationtype that a <see cref="ISecurityTokenValidator"/> will use if nothing is set.
/// This is the default value of <see cref="ClaimsIdentity.AuthenticationType"/> when creating a <see cref="ClaimsIdentity"/>.
/// The value is <c>"AuthenticationTypes.Federation"</c>.
/// To change the value, set <see cref="AuthenticationType"/> to a different value.
/// </summary>
public static readonly string DefaultAuthenticationType = "AuthenticationTypes.Federation"; // Note: The change was because 5.x removed the dependency on System.IdentityModel and we used a different string which was a mistake.

Expand All @@ -190,7 +204,7 @@ public class TokenValidationParameters
public static readonly TimeSpan DefaultClockSkew = TimeSpan.FromSeconds(300); // 5 min.

/// <summary>
/// Default for the maximm token size.
/// Default for the maximum token size.
/// </summary>
/// <remarks>250 KB (kilobytes).</remarks>
public const Int32 DefaultMaximumTokenSizeInBytes = 1024 * 250;
Expand Down Expand Up @@ -218,7 +232,10 @@ protected TokenValidationParameters(TokenValidationParameters other)
IssuerSigningKeyResolverUsingConfiguration = other.IssuerSigningKeyResolverUsingConfiguration;
IssuerSigningKeys = other.IssuerSigningKeys;
IssuerSigningKeyValidator = other.IssuerSigningKeyValidator;
IssuerSigningKeyValidatorUsingConfiguration = other.IssuerSigningKeyValidatorUsingConfiguration;
IssuerValidator = other.IssuerValidator;
IssuerValidatorAsync = other.IssuerValidatorAsync;
IssuerValidatorUsingConfiguration = other.IssuerValidatorUsingConfiguration;
LifetimeValidator = other.LifetimeValidator;
LogTokenId = other.LogTokenId;
LogValidationExceptions = other.LogValidationExceptions;
Expand All @@ -233,6 +250,7 @@ protected TokenValidationParameters(TokenValidationParameters other)
RoleClaimTypeRetriever = other.RoleClaimTypeRetriever;
SaveSigninToken = other.SaveSigninToken;
SignatureValidator = other.SignatureValidator;
SignatureValidatorUsingConfiguration = other.SignatureValidatorUsingConfiguration;
TokenDecryptionKey = other.TokenDecryptionKey;
TokenDecryptionKeyResolver = other.TokenDecryptionKeyResolver;
TokenDecryptionKeys = other.TokenDecryptionKeys;
Expand Down Expand Up @@ -260,7 +278,7 @@ protected TokenValidationParameters(TokenValidationParameters other)

/// <summary>
/// Initializes a new instance of the <see cref="TokenValidationParameters"/> class.
/// </summary>
/// </summary>
public TokenValidationParameters()
{
LogTokenId = true;
Expand Down Expand Up @@ -518,6 +536,17 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken,
public IssuerValidator IssuerValidator { get; set; }


/// <summary>
/// Gets or sets a delegate that will be used to validate the issuer of the token.
/// </summary>
/// <remarks>
/// If set, this delegate will be called to validate the 'issuer' of the token, instead of default processing.
/// This means that no default 'issuer' validation will occur.
/// Even if <see cref="ValidateIssuer"/> is false, this delegate will still be called.
/// IssuerValidatorAsync takes precedence over <see cref="IssuerValidatorUsingConfiguration"/> and <see cref="IssuerValidator"/>.
/// </remarks>
internal IssuerValidatorAsync IssuerValidatorAsync { get; set; }

/// <summary>
/// Gets or sets a delegate that will be used to validate the issuer of the token.
/// </summary>
Expand Down
Loading

0 comments on commit 2468413

Please sign in to comment.