Skip to content

IdentityModel Validation model change #2711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jennyf19 opened this issue Jul 15, 2024 · 2 comments
Open

IdentityModel Validation model change #2711

jennyf19 opened this issue Jul 15, 2024 · 2 comments
Assignees
Labels

Comments

@jennyf19
Copy link
Collaborator

jennyf19 commented Jul 15, 2024

IdentityModel is responsible for validating SecurityTokens. Validating a SecurityToken requires validating multiple parts and reporting the results. Common parts to validate are the issuer, audience, expiration. Default validation is included for important parts of the SecurityToken. The current model provides extensibility using delegates for validation.

Issues we want to address:

  • Simplify the extensibility model by reducing options.
  • Remove exceptions as the model for control.
  • Current validation and delegates throw exceptions on errors.
  • Current validation logs errors by default.
  • Callers will receive a complete description of how the validation occurred.

Proposal

  • Define a set of objects that return the results of a validation step.
  • Define a new set of delegates that are async and return a result.

Each validation step will return a specialized ValidationResult type that contain details that will provide upper layers to the examine errors with contain exception details, log details with a stacktrace that can be thrown or logged.

Related PRs: #2709, #2688, #2679, #2672, #2671, #2669, #2655

@jennyf19 jennyf19 changed the title IdentityModel AsyncDelegates Model IdentityModel Validation model change Jul 15, 2024
@jennyf19 jennyf19 added the ✏️Design proposal Proposal for a feature label Jul 18, 2024
@jennyf19
Copy link
Collaborator Author

jennyf19 commented Feb 20, 2025

New token validation model

The existing token validation model on Microsoft.IdentityModel relies on the exception based control flow dominant in C#. In some high-performance scenarios where latency is key, this can cause unintended increases when validations fail and throw.

As part of an effort to modernize aspects of the library that have become bloated over the years, we are introducing an alternative token validation model that breaks away from some of the previous behaviors and intends to offer a slimmer and more performant API.

Key points on the new model

  • Performance increase by removing unnecessary exception throwing within the validation itself.
  • Return a result where it is clear whether the validation was successful, including relevant information depending on the case.
    • In success scenarios, provide the validated information for audit purposes.
    • In failure scenarios, provide enough information to identify what part of the token was invalid or could not be validated without the need for further steps, and for diagnostic purposes provide the ability to create an exception that can be thrown and observed.
  • Do not automatically print logs as part of the validation. These can be printed from the validation result using C#'s high-performance logging based on ILogger.
  • Simpler validation parameters object, as the current one has grown in complexity over the years by including multiple parameters for things like ValidIssuer and ValidIssuers, or multiple delegates for the same validation. ValidationParameters is being introduced as an alternative to TokenValidationParameters, to offer a clearer configuration object that enables simplifying the default validation code as well.
  • New APIs provide nullability annotations to simplify code branches when the API can ensure no null will be returned from a method.
  • New async APIs receive a CancellationToken to allow for the cancellation of running validation operations, as expected.

Embracing the Result pattern to remove exceptions on a hot path

In order to remove the exceptions being thrown, we are introducing a ValidationResult type to handle the result of all validation operations.
This type can be thought of as a one of type as ValidationResult<TResult, TError>, though the TError type is initially being fixed to ValidationError.

The ValidationResult object can only be created in one of two ways:

  • As a success object, where TResult is provided.
  • As a failure object, where TError (ValidationError) is provided. These two can never exist at the same time on an instance of this object.

The IsValid property of ValidationResult reflects which of these two scenarios is the current.

The following example attempts to illustrate this:

Creating a ValidationResult

// ValidationResult<TResult> can contain TResult if valid, or ValidationError if not.

ValidationResult<string> issuerValidationResult = "some-issuer"; // valid, creates a successful result with "some-issuer" as the Result
issuerValidationResult.IsValid // true
issuerValidationResult.Result // "some-issuer"
issuerValidationResult.Error // null

ValidationResult<string> issuerValidationResult2 = new IssuerValidationError(...) // valid, creates a failed result with the new instance of IssuerValidationError as the Error, which inherits from ValidationError and adds extra information such as the invalid issuer
issuerValidationResult2.IsValid // false
issuerValidationResult2.Result // null
issuerValidationResult2.Error // the IssuerValidationError instance

Validating a token using the new validation model

During the initial preview, the new validation methods are not exposed publicly in JsonWebTokenHandler, SamlSecurityTokenHandler, or Saml2SecurityTokenHandler, but can be accessed via the experimental interface IResultBasedValidation which all 3 explicitly implement.

string token = "some JWT token";
ValidationParameters validationParameters = new ValidationParameters()
{
    ValidAudiences = ["http://Default.Audience.com"],
    ValidIssuers = ["http://Default.Issuer.com"],
    IssuerSigningKeys = [KeyingMaterial.JsonWebKeyRsa256SigningCredentials.Key]
};
CallContext callContext = new CallContext();

JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();
ValidationResult<ValidatedToken> validationResult = await ((IResultBasedValidation)jsonWebTokenHandler).ValidateTokenAsync(token, validationParameters, callContext, default);

if (validationResult.IsValid)
    // do something with the ValidatedToken returned.
    ValidatedToken validatedToken = validationResult.Result;
else
    // inspect the error, log it to telemetry, etc
    ValidationError validationError = validationResult.Error;

Examples of this can be found in dev/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests_e2e.cs.

Benchmarks

Method Mean Error StdDev Median P95 P90 P100 Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
JsonWebTokenHandler_ValidateTokenAsyncCurrentModel_Success 42.049 us 1.2346 us 2.7614 us 42.673 us 44.91 us 44.73 us 46.52 us 1.00 0.00 0.7629 - 7.23 KB 1.00
JsonWebTokenHandler_ValidateTokenAsyncNewModel_Success 41.554 us 1.2499 us 2.7698 us 42.213 us 44.52 us 44.30 us 45.89 us 0.99 0.10 0.7019 - 6.72 KB 0.93
JsonWebTokenHandler_ValidateTokenAsyncCurrentModel_Failure 22.134 us 0.5585 us 1.2259 us 22.555 us 23.30 us 23.26 us 23.57 us ? ? 0.8545 0.0153 7.91 KB ?
JsonWebTokenHandler_ValidateTokenAsyncNewModel_Failure 9.797 us 0.2967 us 0.6450 us 9.915 us 10.44 us 10.37 us 10.50 us ? ? 0.8392 0.0153 7.78 KB ?
JsonWebTokenHandler_ValidateTokenAsyncCurrentModel_CreateClaims 24.184 us 0.4497 us 0.9776 us 24.039 us 25.80 us 25.35 us 26.84 us 1.00 0.00 1.7700 0.0610 16.49 KB 1.00
JsonWebTokenHandler_ValidateTokenAsyncNewModel_CreateClaims 22.486 us 0.0768 us 0.1669 us 22.513 us 22.74 us 22.70 us 22.77 us 0.93 0.04 1.7395 0.0610 16.05 KB 0.97
JsonWebTokenHandler_ValidateTokenAsyncCurrentModel_SucceedOnThirdAttempt 90.089 us 12.4752 us 26.8542 us 92.713 us 142.90 us 105.99 us 173.30 us 1.00 0.00 2.3804 - 22.18 KB 1.00
JsonWebTokenHandler_ValidateTokenAsyncNewModel_SucceedOnThirdAttempt 58.293 us 4.0626 us 9.0866 us 61.416 us 65.48 us 64.87 us 65.72 us 0.71 0.30 2.3804 - 22.28 KB 1.00
JsonWebTokenHandler_ValidateTokenAsyncCurrentModel_SucceedOnFifthAttempt 69.650 us 9.0710 us 19.3311 us 62.202 us 128.34 us 79.92 us 143.15 us 1.00 0.00 3.9063 - 37.13 KB 1.00
JsonWebTokenHandler_ValidateTokenAsyncNewModel_SucceedOnFifthAttempt 82.433 us 2.1832 us 4.7461 us 83.732 us 87.97 us 86.24 us 89.80 us 1.24 0.21 4.0894 0.0610 37.84 KB 1.02

@Kyle0654
Copy link

Any chance of getting an async version of IssuerSigningKeyResolver (and similar for TokenDecryptionKeyResolver)? We've been waiting years for this functionality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants