AzureAD / azure-activedirectory-identitymodel-extensions-for-dotnet

IdentityModel extensions for .Net
MIT License
1.05k stars 396 forks source link

Add Async pattern for TokenValidation #468

Open MicahZoltu opened 8 years ago

MicahZoltu commented 8 years ago

There are a number of places where JWTSecurityTokenHandler calls into user provided methods that may be doing some IO (e.g., database or remote calls) but they do so in a synchronous way.

One example of such a place is with JWT signature validation. The user can provide their own SignatureValidator or IssuerSigningKeyResolver. The act of validating a signature or resolving signing keys may require an interaction with an external server.

For a real-world example, Google rotates its signing keys regularly (rumor is daily) and you can get the latest public key in JWK format here. Unfortunately, it is not possible to know when they are going to roll their keys so an application that is attempting to validate Google issued JWTs will need to hit that endpoint to retrieve the latest signing key. The application could be optimized to cache the signing keys but that is still an async operation and it is entirely up to Google as to how often they rotate their keys (they could start rotating them for every request if they wanted).

Another real-world example once again with Google's authentication, Google supports sending the token to here for validation rather rather than doing the validation locally. If one wants to use this mechanism then every token validation would be a remote call.

Unfortunately, I recognize that the current API is very async un-friendly and the interfaces it implements are also not async-friendly but I wanted to get this filed anyway in hopes that at some point things can be improved to support modern asynchronous use cases.

brentschmaltz commented 2 years ago

@benmccallum @mendhak are you using ConfigurationManager to obtain metadata? ConfigurationManager supports obtaining metadata on a schedule and one-off refresh.

Obtaining metadata is async.

benmccallum commented 2 years ago

@brentschmaltz , I don't believe we are. It's been a while since I was looking at this and haven't had a chance to revisit.

wilsoncg commented 1 year ago

Hello, what's happened to this issue? It seems there's been no activity for a year.

jennyf19 commented 1 year ago

This is done as part of IdentityModel 7x, please try with latest version and reopen if necessary.

HarelM commented 1 year ago

Can you link to a demo/documentation of this new ability?

bugproof commented 12 months ago

Pro tip: please don't close issues without linking to the commit/demo/documentation

This is still an issue without a proper message of what is done

nathfy commented 12 months ago

I think this is missing info: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/wiki/IdentityModel-7x#sync-over-async

"ValidateToken was a sync method calling an async method, which can lead to threadpool starvation when on the hot path, this method has been deprecated, please use ValidateTokenAsync instead. See issue #2253 for details."

Also note this issue when upgrading the nugets: https://stackoverflow.com/a/77121911

ownerer commented 11 months ago

I'm sorry but I don't see how this issue is actually solved? Looking at the source code for JwtSecurityTokenHandler all this new ValidateTokenAsync method does is wrap the old synchronous method. Everything else is still synchronous including delegates in the TokenValidationParameters class, such as IssuerSigningKeyResolver. I'd expect to be able to pass an asynchronous delegate there; that is still impossible. So getting the signing key from some external resource still has to happen sync-over-async... Eg:

IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>
{
    var result = new List<SecurityKey>();
    //TODO: make this async, cfr https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/468
    var secret = _tokenManager.GetSecret(kid, cancellationToken).Result; // <<<---- BAD!!
    if (secret != null)
    {
        result.Add(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secret.Value)));
    }

    return result;
}
kroymann commented 11 months ago

@ownerer All of the async improvements appear to have gone into JsonWebTokenHandler, which is apparently the new and improved successor to JwtSecurityTokenHandler (see https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/945#issuecomment-405113306 - I sure wish this was better documented as I had no idea JwtSecurityTokenHandler was old until just now). It looks they did the minimal update necessary to have a technically functional implementation of the async methods for JwtSecurityTokenHandler (https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/1810/files#diff-0b1054c790c38d665f14eee85e63889a6c64f63e87ab7ded11b86708f1ed1475), but if you want a proper async-all-the-way-down option, you need to switch to JsonWebTokenHandler.

ownerer commented 11 months ago

Thanks for that info @kroymann , but the ValidateTokenAsync method of JsonWebTokenHandler still takes an instance of that same TokenValidationParameters type, meaning its delegates are still synchronous, so the problem I described remains. You can see how it gets called here. This issue is not resolved.

brentschmaltz commented 11 months ago

@ownerer we are aware of the issue with the sync delegates. We tried out one async, https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/3cae45521ede8fb4c77f9be73887b4818f756f9f/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs#L115 to get a feel for the design.

ownerer commented 11 months ago

@brentschmaltz , aha ok. Any idea when the others will be converted/added? Is that work tracked somewhere, seeing as this issue is closed...

brentschmaltz commented 11 months ago

@ownerer we are fully booked until the new year, but new async delegates that return a typeof ValidationResult to avoid logging or throwing when multiple identity providers are in play and the second one succeeds, is important work that we are planning early next year.

Pikhulya commented 9 months ago

>... This is done as part of IdentityModel 7x, please try with latest version and reopen if necessary. ...

@jennyf19 , @brentschmaltz , please re-open the issue given, as outlined in the few above comments, it is not really fixed. Alternatively, if it is tracked by another tracking work item on GitHub - please share the link.

brentschmaltz commented 9 months ago

@Pikhulya reopened

HarelM commented 5 months ago

Are there any examples I can use in order to change my code to use JsonWebTokenHandler? I can't seem to find any information and I'm not able to wire it up to my app so that it does the validation...

brentschmaltz commented 5 months ago

@HarelM marked this a .net9 as we are going to be modifying our Delegates to make them async and return a ValidationResult rather than throwing.

HarelM commented 5 months ago

Yeah, I'm not going to wait for .net 9 just to find out the documentation is poor again and that I can't figure out how to properly migrate my current code. While in most cases I disagree with the approach of "if it ain't broken don't fix it", in this case I gave up. Here's my commit BTW that only fixes what I needed in terms of validating the token only for authorized routes https://github.com/IsraelHikingMap/Site/commit/bae22472aab6af88716ae740a494ea97e334d2b1

jennyf19 commented 5 months ago

@HarelM we will aim to get this into our current workstream. by referring to .NET 9, we do not mean Nov 2024. Please follow this issue for updates. Appreciate the feedback and we will do better with documentation moving forward.