-
Notifications
You must be signed in to change notification settings - Fork 218
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
[do not merge] feedback PR for perf improvements #1084
Conversation
idToken = headers[AppServicesAuthIdTokenHeader]; | ||
} | ||
string? idToken = null; | ||
if (headers.ContainsKey(AppServicesAuthIdTokenHeader)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TryGetValue results in a single dictionary lookup rather than two lookups when using ContainsKey with subsequent access.
if (headers.ContainsKey(AppServicesAuthIdTokenHeader)) | |
if (headers.TryGetValue(AppServicesAuthIdTokenHeader, out var idToken)) |
Kudos, SonarCloud Quality Gate passed! 0 Bugs No Coverage information |
ClaimsPrincipal? user = null, | ||
TokenAcquisitionOptions? tokenAcquisitionOptions = null) | ||
{ | ||
string? idToken = AppServicesAuthenticationInformation.GetIdToken(CurrentHttpContext?.Request?.Headers!); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CurrentHttpContext needs to be locked on to avoid threading issues with external code.
string accessToken = await GetAccessTokenForUserAsync(scopes, tenantId, userFlow, user, tokenAcquisitionOptions).ConfigureAwait(false); | ||
string expiration = userClaims.FindFirstValue("exp"); | ||
DateTimeOffset dateTimeOffset = (expiration != null) | ||
? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiration, CultureInfo.InvariantCulture)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
long.parse could throw if the claim has a broken expiration string. long.TryParse would be safer.
dateTimeOffset, | ||
dateTimeOffset, | ||
userClaims?.GetTenantId(), | ||
userClaims != null ? new Account(userClaims) : null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are three null checks on userClaims here.
userClaims?.GetDisplayName()
userClaims?.GetTenantId()
userClaims != null
It would be faster to do the check once and set three variables.
string? displayName;
string? tenantId;
Account? account;
if (userClaims != null)
{
displayName = userClaims.GetDisplayName();
tenantId = userClaims.GetTenantId();
account = new Account(userClaims);
}
else
{
displayName = null;
tenantId = null;
account = null;
}
} | ||
|
||
/// <inheritdoc/> | ||
public async Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(IEnumerable<string> scopes, MsalUiRequiredException msalServiceException, HttpResponse? httpResponse = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the async keyword can be removed when all the function does is throw. Doing so would avoid the need to suppress a warning about async functions needing something awaited.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait, I already did that in this file in my other PR.
var userFlow = options.DefaultUserFlow; | ||
return new Uri(baseUri, new PathString($"{pathBase}/{domain}/{userFlow}/v2.0")).ToString(); | ||
} | ||
else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't really matter that much, but the else keyword and block nesting isn't needed because the if (options.IsB2C)
block had a return statement.
internal static string EnsureAuthorityIsV2(string authority) | ||
{ | ||
authority = authority.Trim().TrimEnd('/'); | ||
if (!authority.EndsWith("v2.0", StringComparison.InvariantCulture)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For strings that are language and culture agnostic, StringComparison.Ordinal should be used. It is faster and more targeted to the usecase here: https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings
} | ||
internal static string EnsureAuthorityIsV2(string authority) | ||
{ | ||
authority = authority.Trim().TrimEnd('/'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String allocations tend to be a death by 1000 cuts issue at scale. Instead of TrimEnd('/'), I suggest adding "v2.0" or "/v2.0" based upon the presence of the trailing slash. Doing so should skip one of the string reallocations.
throw new InvalidOperationException( | ||
string.Format( | ||
CultureInfo.InvariantCulture, | ||
IDWebErrorMessage.ProvideEitherScopeKeySectionOrScopes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error messages should really be localized in order to promote the most inclusive api experiences.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once localized, InvariantCulture is probably not the correct culture to use.
if (!string.IsNullOrWhiteSpace(ScopeKeySection)) | ||
{ | ||
// Load the injected IConfiguration | ||
IConfiguration configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetRequiredService will throw if the service isn't registered. I recommend changing it to GetService to avoid an unhelpful error.
} | ||
else | ||
{ | ||
if (Options.IsB2C) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if block can be less nested by combining towards "else if" which will slightly improve readability.
// Correlation ID: f99deff4-f43b-43cc-b4e7-36141dbaf0a0 | ||
// Timestamp: 2018-03-05 02:49:35Z | ||
// ', error_uri: 'error_uri is null'. | ||
if (context.Failure is OpenIdConnectProtocolException && context.Failure.Message.Contains(ErrorCodes.B2CForgottenPassword)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
context.Failure is OpenIdConnectProtocolException can be evaluated once rather than re-evaluated in multiple if statements.
} | ||
|
||
return _userFlowToIssuerAddress[userFlow]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing this will be twice as fast:
} | |
if (!_userFlowToIssuerAddress.TryGetValue(userFlow, out var issuerAddress)) | |
{ | |
issuerAddress = context.ProtocolMessage.IssuerAddress.ToLowerInvariant() | |
.Replace($"/{defaultUserFlow?.ToLowerInvariant()}/", $"/{userFlow.ToLowerInvariant()}/"); | |
_userFlowToIssuerAddress[userFlow] = issuerAddress; | |
} | |
return issuerAddress; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol...i had fixed that when doing the ContainsKey fixes.
{ | ||
return (false, new UnauthorizedObjectResult(new ProblemDetails | ||
{ | ||
Title = "Authorization failed.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The title string needs to be localized.
|
||
private static readonly string DoubleBase64PadCharacter = string.Format(CultureInfo.InvariantCulture, "{0}{0}", Base64PadCharacter); | ||
private static readonly string DoubleBase64PadCharacter = string.Format(CultureInfo.InvariantCulture, "{0}{0}", Base64PadCharacter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good example of where string.Format isn't needed
private static readonly string DoubleBase64PadCharacter = string.Format(CultureInfo.InvariantCulture, "{0}{0}", Base64PadCharacter); | |
private const string DoubleBase64PadCharacter = Base64PadCharacter + Base64PadCharacter; |
/// User assigned managed identity client ID (as opposed to system assigned managed identity) | ||
/// See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-manage-ua-identity-portal. | ||
/// </summary> | ||
public static string? UserAssignedManagedIdentityClientId { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having this be static and externally settable opens up race conditions where someone thinks it is populated when it may not be, and then makes incorrect decisions based upon that. Saying it is not used that way now is not sufficient. This is a high risk of misuse for future contributors. Recommendation is to make it an instance field on a new interface that is not settable externally, then access it through dependency injection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will raise an issue for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId); | ||
/// HttpContext.User = ClaimsPrincipalExtension.FromTenantIdAndObjectId(subscription.TenantId, | ||
/// subscription.UserId); | ||
/// string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sample looks a bit broken with missing end brackets and overwriting of HttpContext.User with each iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great eye.
internal class ClientInfo | ||
{ | ||
[JsonPropertyName(ClaimConstants.UniqueObjectIdentifier)] | ||
public string UniqueObjectIdentifier { get; set; } = null!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setting to null! seems like we are incorrectly trying to override the type. The reality is that json can omit the field which would result in a null string.
/// <returns>User flow ID of the identity, or <c>null</c> if it cannot be found.</returns> | ||
public static string? GetUserFlowId(this ClaimsPrincipal claimsPrincipal) | ||
{ | ||
if (claimsPrincipal == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these extension methods could be deduplicated into calling a single private shared method:
private static string? GetClaimValue(ClaimsPrincipal? claimsPrincipal, params string[] claimNames)
{
if (claimsPrincipal == null)
{
throw new ArgumentNullException(nameof(claimsPrincipal));
}
for (var i = 0; i < claimNames.Length; i++)
{
var currentValue = claimsPrincipal.FindFirstValue(claimNames[i]);
if (!string.IsNullOrEmpty(currentValue))
{
return currentValue;
}
}
return null;
}
/// <summary> | ||
/// General constants for Microsoft Identity Web. | ||
/// </summary> | ||
public static class Constants |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are many constants throughout the codebase. It may be worthwhile to rename this class to be more descriptive about the specific constants it holds.
public const string SerializingSessionCache = "Serializing session {0}, cache key {1}. "; | ||
public const string ClearingSessionCache = "Clearing session {0}, cache key {1}. "; | ||
// Caching | ||
public const string DeserializingSessionCache = "Deserializing session {0}, cache key {1}. "; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider using LoggerMessage as described at https://docs.microsoft.com/en-us/dotnet/core/extensions/high-performance-logging instead of doing costly message formats into strings.
|
||
bool IsIosVersion(int major) | ||
{ | ||
string regex = @"\(iP.+; CPU .*OS (\d+)[_\d]*.*\) AppleWebKit\/"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is in the hotpath of requests. As such, it would likely speed up by 5-7x if regular expressions were avoided where possible. We did a similar change in a planet scale service and sped the code up by 7x.
{ | ||
_tokenAcquisition = tokenAcquisition; | ||
_namedDownstreamWebApiOptions = namedDownstreamWebApiOptions; | ||
_httpClient = httpClient; | ||
#pragma warning disable CA1062 // Validate arguments of public methods |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This warning suppression might be able to be removed now that strict null types is enabled.
httpRequestMessage.Headers.Add( | ||
Constants.Authorization, | ||
authResult.CreateAuthorizationHeader()); | ||
response = await _httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the response var isn't used outside the scope of the using block. It can be removed and replaced by a direct return statement in front of the awaited call.
user, | ||
new StringContent(JsonSerializer.Serialize(input), Encoding.UTF8, "application/json")).ConfigureAwait(false); | ||
|
||
try |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using exceptions for control flow is a very inefficient way of doing things. I recommend instead checking if (!response.IsSuccessStatusCode) and only then throwing the HttpRequestException.
} | ||
private static StringContent ConvertFromInput<TInput>(TInput input) | ||
{ | ||
return new StringContent(JsonSerializer.Serialize(input), Encoding.UTF8, "application/json"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming the input is intended to be default json is something which disallows calling downstream apis using other formats such as msgpack. Msgpack is a popular format used within internal containerized servers that need more performance when communicating amongst each other.
/// Options passed-in to call downstream web APIs. To call Microsoft Graph, see rather | ||
/// <c>MicrosoftGraphOptions</c> in the <c>Microsoft.Identity.Web.MicrosoftGraph</c> assembly. | ||
/// </summary> | ||
public class DownstreamWebApiOptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is clonable, so why not have it implement ICloneable?
/// <code> | ||
/// public async Task<IEnumerable<MyItem>> GetAsync() | ||
/// { | ||
/// return await _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to illustrate best practices, the task should be returned instead of awaited in the example.
/// <c>true</c> if the specified string collection contains any; otherwise, <c>false</c>.</returns> | ||
public static bool ContainsAny(this string searchFor, params string[] stringCollection) | ||
{ | ||
foreach (string str in stringCollection) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be done faster, but probably shouldn't be. The faster implementation I'm thinking of involves char operations and a trie built from the string collection inputs..
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. | ||
string fullVersion = typeof(IdHelper).GetTypeInfo().Assembly.FullName; | ||
string fullVersion = typeof(IdHelper).GetTypeInfo().Assembly.FullName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the FullName field parsed rather than using the .Version property of the Assembly object?
scopes.Union(additionalBuiltInScopes).ToList()); | ||
properties.SetParameter<ICollection<string>>( | ||
OpenIdConnectParameterNames.Scope, | ||
scopes.Union(additionalBuiltInScopes).ToList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LINQ is super slow. Like, so slow, I would recommend people almost never use it when there is a non-verbose alternative. In this case, there is a data structure that perfectly does what we want in an optimized way. That data structure is the HashSet which happens to have a UnionWith method and already implements ICollection.
/// <summary> | ||
/// Handler for Blazor specific APIs to handle incremental consent | ||
/// and conditional access. | ||
/// </summary> | ||
#pragma warning disable SA1402 // File may only contain a single type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the type be moved to its own file in order to make it easier to find on the github folder view?
#pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case | ||
(!IsBlazorServer ? CreateBaseUri(_httpContextAccessor.HttpContext.Request) : | ||
(!IsBlazorServer ? CreateBaseUri(_httpContextAccessor.HttpContext.Request) : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everytime HttpContext is touched, it will need somesort of lock in this code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
else | ||
{ | ||
throw exception; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is a problem because it causes the original stacktrace to be lost. The exception needs to be thrown with throw;
instead of throw exception;
which means it can't be done inside this method and instead needs to be done from a catch block directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are given the exception to process here for incremental consent, not sure how we can move this to a try/catch. otherwise, developers would have to do this, and it becomes too complicated.
|
||
effectiveScopes = effectiveScopes.Union(additionalBuiltInScopes).ToArray(); | ||
effectiveScopes = effectiveScopes.Union(additionalBuiltInScopes).ToArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ToArray results in an unnecessary allocation.
redirectUri = NavigationManager.Uri; | ||
} | ||
else | ||
{ | ||
#pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HttpContext can definitely be null here if the library is attempted to be used within a worker service. With that in mind, the warning should be addressed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is only for blazorwasm scenarios, so we will always have the httpContext.
{ | ||
var state = await Provider.GetAuthenticationStateAsync().ConfigureAwait(false); | ||
Service.User = state.User; | ||
Service.IsBlazorServer = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsBlazorServer might not be correct or am I reading this wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is only for blazorwasm...it's a long story.
IsValidTidInLocalPath(tenantId, actualIssuerUri); | ||
} | ||
catch | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
faults should minimally be logged.
jwtSecurityToken.Payload.TryGetValue(ClaimConstants.TenantId, out object? tenantId); | ||
if (tenantId != null) | ||
{ | ||
return (string)tenantId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in case someone is trying to break things, the cast would be safer as tenantId as string;
/// <summary> | ||
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. | ||
/// </summary> | ||
private Func<AuthenticationFailedContext, Task> s_onAuthenticationFailed = null!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This naming convention doesn't match the rest of the codebase. Historically, s_ meant static, which makes this naming a bit misleading.
.Distinct(); | ||
_issuerValidators[authorityHost] = new AadIssuerValidator(aliases); | ||
// Add issuer aliases of the chosen authority to the cache | ||
IEnumerable<string> aliases = issuerMetadata.Metadata |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use the unLINQ treatment.
_logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnRedirectToIdentityProviderAsync))); | ||
} | ||
|
||
private void DisplayProtocolMessage(OpenIdConnectMessage message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this please be ifdef'd out of existence do it is excluded from the build in release mode? The reflection used in this method makes it very slow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
developers have to set it explicitly, and i doubt they would use it in release mode. we can make a comment in the wiki, and we could add an if/def too...sometimes we might need the diagnostics in release mode.
} | ||
private void ValidateEffectiveScopes(AuthorizationFilterContext context) | ||
{ | ||
if (_effectiveAcceptedScopes == null || !_effectiveAcceptedScopes.Any()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.Any() is much slower than string[].Length > 0;
string.Join(",", _effectiveAcceptedScopes)); | ||
context.HttpContext.Response.WriteAsync(message); | ||
context.HttpContext.Response.CompleteAsync(); | ||
throw new UnauthorizedAccessException(message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does an exception need to be thrown when the response was already completed in the line above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so the rest of the controller action is not done. if the scope is not correct, we want to avoid the rest of the controller action from happening. there might be a better way to handle it, will take a look. if you have any suggestions, let us know.
/// <param name="acceptedRoles">Roles accepted by this web API.</param> | ||
/// <remarks>When the roles don't match, the response is a 403 (Forbidden), | ||
/// because the app does not have the expected roles.</remarks> | ||
public static void ValidateAppRole(this HttpContext context, params string[] acceptedRoles) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
httpContext needs locking to avoid concurrent reads with external code.
|
||
if (result == null) | ||
{ | ||
var measure = await Task.Run(async () => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Task.Run is usually something that shouldn't be done in an already async function for perf/context switching reasons. It looks like it is only done to measure things. I suggest removing task.run and measuring with StopWatch.StartNew.
|
||
private readonly ReaderWriterLockSlim _sessionLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); | ||
} | ||
private readonly ReaderWriterLockSlim _sessionLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ReaderWriterLockSlim is disposable. Thus MsalSessionTokenCacheProvider should also be disposable.
var tokenValidatedHandler = options.Events.OnTokenValidated; | ||
options.Events.OnTokenValidated = async context => | ||
{ | ||
if (!microsoftIdentityOptions.AllowWebApiToBeAuthorizedByACL && !context!.Principal!.Claims.Any(x => x.Type == ClaimConstants.Scope) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rather than iterating the claims 4 times, it can all be done in one .Any call.
No description provided.