Skip to content
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

ValidateTokenAsync - New Path: Refactor result types #2794

Merged
merged 35 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4067f67
Adding benchmark for new ValidateTokenAsync model vs old.
Aug 13, 2024
ea1c1be
Update benchmark/Microsoft.IdentityModel.Benchmarks/ValidateTokenAsyn…
FuPingFranco Aug 13, 2024
a7d2680
Updated class naming to be more explicit and added benchmark using clone
Aug 14, 2024
4f80ff7
Added benchmarks to compare performance on multiple validation scenarios
iNinja Aug 16, 2024
ae58f1b
Removed StackFrame from ValidationResult
iNinja Aug 16, 2024
a61ca89
Work in progress
iNinja Aug 20, 2024
c1e1ee9
Added project
iNinja Aug 20, 2024
e89a354
Added missing await
iNinja Aug 20, 2024
ea0fc3c
Removed tags, added implicit initialisers for Result type. Updated Re…
iNinja Aug 21, 2024
227f763
Merge branch 'dev' into iinglese/refactor-results
iNinja Aug 21, 2024
d87e8dc
Removed IdentityComparer methods relating to removed result types. Up…
iNinja Aug 21, 2024
395c781
Removed ITokenValidationError interface. Updated tests
iNinja Aug 22, 2024
196daed
Replaced TokenValidationError with ExceptionDetails. Added stack fram…
iNinja Aug 23, 2024
48dc149
Removed optionality from CancellationToken parameter
iNinja Aug 23, 2024
93492ee
Updated tests
iNinja Aug 23, 2024
5017401
Added ClaimsIdentity to ValidationResult
iNinja Aug 23, 2024
3c2ce12
Merge branch 'dev' into iinglese/refactor-results
iNinja Aug 23, 2024
90fce75
Updated benchmarks to match the result types and added CancellationToken
iNinja Aug 23, 2024
09bc198
Removed test consoleapp, re-grouped benchmarks for better comparison
iNinja Aug 23, 2024
32dcd6d
Removed unit type
iNinja Aug 23, 2024
72e9574
Removed TokenValidationError since it is no longer used
iNinja Aug 25, 2024
925ee09
Restored ExceptionType type name
iNinja Aug 25, 2024
f932e31
Restored ExceptionFromType method in ExceptionDetail
iNinja Aug 25, 2024
6d4ac4d
Override Result's empty initialiser and annotate it to prevent wrong …
iNinja Aug 26, 2024
44935be
Removed commented code.
iNinja Aug 26, 2024
7abcffe
Commented empty if statement
iNinja Aug 26, 2024
f180111
Added cached stack frames and nullability annotations for ValidateTok…
iNinja Aug 26, 2024
9ba4c7b
Added stack frames for ReadToken and DecryptToken
iNinja Aug 26, 2024
168ee84
Added ValidationFailureType to ExceptionDetail
iNinja Aug 26, 2024
b407bad
Updated tests to use ValidationFailureType
iNinja Aug 26, 2024
7988568
Update src/Microsoft.IdentityModel.Abstractions/Result.cs
iNinja Aug 27, 2024
fb2c6ee
Moved Result to Tokens project, made it internal. Reverted TargetFram…
iNinja Aug 27, 2024
c84d348
Addressed PR feedback around argument null exceptions
iNinja Aug 27, 2024
1bb46d9
Merge branch 'dev' into iinglese/refactor-results
iNinja Aug 27, 2024
4ddef57
Addressed PR comments. Removed unused imports. Increased epsilon for …
iNinja Aug 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmark/Microsoft.IdentityModel.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private static void DebugThroughTests()
ValidateTokenAsyncTests validateTokenAsyncTests = new ValidateTokenAsyncTests();
validateTokenAsyncTests.Setup();
TokenValidationResult tokenValidationResult = validateTokenAsyncTests.JsonWebTokenHandler_ValidateTokenAsyncWithTVP().Result;
TokenValidationResult validationResult = validateTokenAsyncTests.JsonWebTokenHandler_ValidateTokenAsyncWithVP().Result;
bool validationResult = validateTokenAsyncTests.JsonWebTokenHandler_ValidateTokenAsyncWithVP().Result;
var claims = validateTokenAsyncTests.JsonWebTokenHandler_ValidateTokenAsyncWithTVP_CreateClaims();

ValidateSignedHttpRequestAsyncTests validateSignedHttpRequestAsyncTests = new ValidateSignedHttpRequestAsyncTests();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
Expand Down Expand Up @@ -82,13 +83,13 @@ public void Setup()
_callContext = new CallContext();
}

[BenchmarkCategory("ValidateTokenAsyncWithTokenValidationParameters"), Benchmark]
public async Task<TokenValidationResult> JwtSecurityTokenHandler_ValidateTokenAsync() => await _jwtSecurityTokenHandler.ValidateTokenAsync(_jws, _tokenValidationParameters).ConfigureAwait(false);
[BenchmarkCategory("ValidateTokenAsync_Success"), Benchmark]
public async Task<TokenValidationResult> JwtSecurityTokenHandler_ValidateTokenAsync() => await _jwtSecurityTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _tokenValidationParameters).ConfigureAwait(false);

[BenchmarkCategory("ValidateTokenAsyncWithTokenValidationParameters"), Benchmark(Baseline = true)]
public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncWithTVP() => await _jsonWebTokenHandler.ValidateTokenAsync(_jws, _tokenValidationParameters).ConfigureAwait(false);
[BenchmarkCategory("ValidateTokenAsync_Success"), Benchmark(Baseline = true)]
public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncWithTVP() => await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _tokenValidationParameters).ConfigureAwait(false);

[BenchmarkCategory("ValidateTokenAsyncWithTokenValidationParameters"), Benchmark]
[BenchmarkCategory("ValidateTokenAsync_Success"), Benchmark]
public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncWithTVPUsingModifiedClone()
{
var tokenValidationParameters = _tokenValidationParameters.Clone();
Expand All @@ -98,7 +99,16 @@ public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncW
return await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, tokenValidationParameters).ConfigureAwait(false);
}

[BenchmarkCategory("ValidateTokenAsyncWithTokenValidationParameters"), Benchmark]
[BenchmarkCategory("ValidateTokenAsync_Success"), Benchmark]
public async Task<bool> JsonWebTokenHandler_ValidateTokenAsyncWithVP()
{
// Because ValidationResult is an internal type, we cannot return it in the benchmark.
iNinja marked this conversation as resolved.
Show resolved Hide resolved
// We return a boolean instead until the type is made public.
ValidationResult result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _validationParameters, _callContext, CancellationToken.None).ConfigureAwait(false);
return result.IsValid;
}

[BenchmarkCategory("ValidateTokenAsync_FailTwiceBeforeSuccess"), Benchmark(Baseline = true)]
public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncWithTVP_SucceedOnThirdAttempt()
{
TokenValidationResult result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidTokenValidationParameters).ConfigureAwait(false);
Expand All @@ -108,7 +118,7 @@ public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncW
return result;
}

[BenchmarkCategory("ValidateTokenAsyncWithTokenValidationParameters"), Benchmark]
[BenchmarkCategory("ValidateTokenAsync_FailTwiceBeforeSuccess"), Benchmark]
public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncWithTVPUsingClone_SucceedOnThirdAttempt()
{
TokenValidationResult result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidTokenValidationParameters.Clone()).ConfigureAwait(false);
Expand All @@ -118,7 +128,17 @@ public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncW
return result;
}

[BenchmarkCategory("ValidateTokenAsyncWithTokenValidationParameters"), Benchmark]
[BenchmarkCategory("ValidateTokenAsync_FailTwiceBeforeSuccess"), Benchmark]
public async Task<bool> JsonWebTokenHandler_ValidateTokenAsyncWithVP_SucceedOnThirdAttempt()
{
ValidationResult result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, CancellationToken.None).ConfigureAwait(false);
result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, CancellationToken.None).ConfigureAwait(false);
result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _validationParameters, _callContext, CancellationToken.None).ConfigureAwait(false);

return result.IsValid;
}

[BenchmarkCategory("ValidateTokenAsync_FailFourTimesBeforeSuccess"), Benchmark(Baseline = true)]
public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncWithTVP_SucceedOnFifthAttempt()
{
TokenValidationResult result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidTokenValidationParameters).ConfigureAwait(false);
Expand All @@ -130,7 +150,7 @@ public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncW
return result;
}

[BenchmarkCategory("ValidateTokenAsyncWithTokenValidationParameters"), Benchmark]
[BenchmarkCategory("ValidateTokenAsync_FailFourTimesBeforeSuccess"), Benchmark]
public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncWithTVPUsingClone_SucceedOnFifthAttempt()
{
TokenValidationResult result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidTokenValidationParameters.Clone()).ConfigureAwait(false);
Expand All @@ -142,40 +162,31 @@ public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncW
return result;
}

[BenchmarkCategory("ValidateTokenAsyncWithValidationParameters"), Benchmark(Baseline = true)]
public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncWithVP() => await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _validationParameters, _callContext, null).ConfigureAwait(false);

[BenchmarkCategory("ValidateTokenAsyncWithValidationParameters"), Benchmark]
public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncWithVP_SucceedOnThirdAttempt()
[BenchmarkCategory("ValidateTokenAsync_FailFourTimesBeforeSuccess"), Benchmark]
public async Task<bool> JsonWebTokenHandler_ValidateTokenAsyncWithVP_SucceedOnFifthAttempt()
{
var result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, null).ConfigureAwait(false);
result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, null).ConfigureAwait(false);
return await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _validationParameters, _callContext, null).ConfigureAwait(false);
}
ValidationResult result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, CancellationToken.None).ConfigureAwait(false);
result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, CancellationToken.None).ConfigureAwait(false);
result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, CancellationToken.None).ConfigureAwait(false);
result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, CancellationToken.None).ConfigureAwait(false);
result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _validationParameters, _callContext, CancellationToken.None).ConfigureAwait(false);

[BenchmarkCategory("ValidateTokenAsyncWithValidationParameters"), Benchmark]
public async Task<TokenValidationResult> JsonWebTokenHandler_ValidateTokenAsyncWithVP_SucceedOnFifthAttempt()
{
var result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, null).ConfigureAwait(false);
result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, null).ConfigureAwait(false);
result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, null).ConfigureAwait(false);
result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _invalidValidationParameters, _callContext, null).ConfigureAwait(false);
return await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _validationParameters, _callContext, null).ConfigureAwait(false);
return result.IsValid;
}

[BenchmarkCategory("ValidateTokenAsyncClaimAccess"), Benchmark]
public async Task<List<Claim>> JsonWebTokenHandler_ValidateTokenAsyncWithVP_CreateClaims()
[BenchmarkCategory("ValidateTokenAsyncClaimAccess"), Benchmark(Baseline = true)]
public async Task<List<Claim>> JsonWebTokenHandler_ValidateTokenAsyncWithTVP_CreateClaims()
{
var result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _validationParameters, _callContext, null).ConfigureAwait(false);
var result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _tokenValidationParameters).ConfigureAwait(false);
var claimsIdentity = result.ClaimsIdentity;
var claims = claimsIdentity.Claims;
return claims.ToList();
}

[BenchmarkCategory("ValidateTokenAsyncClaimAccess"), Benchmark]
public async Task<List<Claim>> JsonWebTokenHandler_ValidateTokenAsyncWithTVP_CreateClaims()
public async Task<List<Claim>> JsonWebTokenHandler_ValidateTokenAsyncWithVP_CreateClaims()
{
var result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _tokenValidationParameters).ConfigureAwait(false);
var result = await _jsonWebTokenHandler.ValidateTokenAsync(_jwsExtendedClaims, _validationParameters, _callContext, CancellationToken.None).ConfigureAwait(false);
var claimsIdentity = result.ClaimsIdentity;
var claims = claimsIdentity.Claims;
return claims.ToList();
Expand Down
151 changes: 151 additions & 0 deletions src/Microsoft.IdentityModel.Abstractions/Result.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

#nullable enable
namespace Microsoft.IdentityModel.Abstractions
{
/// <summary>
/// Represents a result that can be either successful or unsuccessful.
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <typeparam name="TError"></typeparam>
public readonly struct Result<TResult, TError> : IEquatable<Result<TResult, TError>>
iNinja marked this conversation as resolved.
Show resolved Hide resolved
iNinja marked this conversation as resolved.
Show resolved Hide resolved
{
readonly TResult? _result;
readonly TError? _error;

/// <summary>
/// Creates a successful result.
/// </summary>
/// <param name="result">The value associated with the success.</param>
public Result(TResult result)
{
_result = result;
_error = default;
IsSuccess = true;
}

/// <summary>
/// Creates an error result.
/// </summary>
/// <param name="error">The error associated with the failure.</param>
public Result(TError error)
{
_result = default;
_error = error;
IsSuccess = false;
}

/// <summary>
/// Creates a successful result implicitly from the value.
/// </summary>
/// <param name="result">The value to be stored in the result.</param>
public static implicit operator Result<TResult, TError>(TResult result) => new(result);

/// <summary>
/// Creates an error result implicitly from the error value.
/// </summary>
/// <param name="error">The error to be stored in the result.</param>
public static implicit operator Result<TResult, TError>(TError error) => new(error);

/// <summary>
/// Gets a value indicating whether the result is successful.
/// </summary>
readonly public bool IsSuccess { get; }
iNinja marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Unwraps the result.
/// </summary>
/// <returns>The wrapped result value.</returns>
/// <remarks>This method is only valid if the result type is successful.</remarks>
/// <exception cref="InvalidOperationException">Thrown if attempted to unwrap the value from a failed result.</exception>
public TResult UnwrapResult() => IsSuccess ? _result! : throw new InvalidOperationException("Cannot unwrap error result");

/// <summary>
/// Unwraps the error.
/// </summary>
/// <returns>The wrapped error value.</returns>
/// <remarks>This method is only valid if the result type is unsuccessful.</remarks>
/// <exception cref="InvalidOperationException">Thrown if attempted to unwrap an error from a successful result.</exception>
public TError UnwrapError() => IsSuccess ? throw new InvalidOperationException("Cannot unwrap success result") : _error!;

/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
iNinja marked this conversation as resolved.
Show resolved Hide resolved
public override bool Equals(object? obj)
{
if (obj is Result<TResult, TError> other)
{
return Equals(other);
}

return false;
}

/// <summary>
///
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override int GetHashCode()
{
if (IsSuccess)
return _result!.GetHashCode();
else
return _error!.GetHashCode();
}

/// <summary>
///
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
public static bool operator ==(Result<TResult, TError> left, Result<TResult, TError> right)
{
return left.Equals(right);
}

/// <summary>
///
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
public static bool operator !=(Result<TResult, TError> left, Result<TResult, TError> right)
{
return !(left == right);
}

/// <summary>
///
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public bool Equals(Result<TResult, TError> other)
{
if (other.IsSuccess != IsSuccess)
return false;

if (IsSuccess)
return _result!.Equals(other._result);
else
return _error!.Equals(other._error);
}

/// <summary>
/// Casts the result to a <see cref="Result{TResult, TError}"/>.
/// </summary>#
/// <remarks>Required for compatibility, see CA2225 for more information</remarks>
/// <returns>The existing instance.</returns>
public Result<TResult, TError> ToResult()
{
return this;
}
}
}
#nullable restore
Loading
Loading