azure-activedirectory-identitymodel-extensions-for-dotnet
azure-activedirectory-identitymodel-extensions-for-dotnet copied to clipboard
Add Async pattern for TokenValidation
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.
@Zoltu I agree with you that off-box validation is a real scenario. We should have an async pattern up and down the call graph to realize this pattern. We stopped at metadata retrieval.
We will need to add an new return type: ValidationResult so the 'out' param is not needed.
Include this issue with this fix. https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/566
Modified title to reflect that this is general in nature across all token types. Perhaps in SecurityTokenHandler
Do we have an ETA on this?
@shagenhokie not yet
@brentschmaltz the method that handles jwt authentication is async so I think it shouldn't be that difficult to add? https://github.com/aspnet/Security/blob/beaa2b443d46ef8adaf5c2a89eb475e1893037c2/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L107-L128
It's a limitation if you want to implement token invalidation and call redis or any other db asynchronously inside the handler: https://stackoverflow.com/questions/52476325/how-to-invalidate-tokens-after-password-change/52476727#52476727
Because ValidateToken
token uses out
parameter and async methods can't be used with out parameter that would be a breaking change, ISecurityTokenValidator
would need a method which returns Task anyway. The best workaround to avoid breaking change I can think of is:
public async Task<(ClaimsPrincipal, SecurityToken)> ValidateTokenAsync(string token,
TokenValidationParameters validationParameters)
{
var claimsPrincipal = base.ValidateToken(token, validationParameters, out var validatedToken);
return (claimsPrincipal, validatedToken);
}
public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
var tuple = ValidateTokenAsync(token, validationParameters).Result;
validatedToken = tuple.Item2;
return tuple.Item1;
}
@fragilethoughts I agree we need async, particularly with delegation to validation services, such as KeyVault and Google. Not only do we need ValidateTokenAsync, we need async on our Crypto such as SignatureProviders and KeyWrapProviders.
This is a feature we are thinking about for 5.3.1, but realistically it may be later. The other issue is we want to remove references to any libraries other than those shipped by Microsoft, such as JSON.net. With many JwtSecurityToken and JwtSecurityTokenHandler API's using JSON.net, we created a new assembly M.IM.JsonWebTokens (free from JSON.net) where ValidateToken returns a structure TokenValidationResult and can have easily have an async version. We are free to invent here.
Sounds good
Is the plan to create an async version of JwtSecurityTokenHandler, so it would be possible to call WriteToken asynchronously? It would help a lot, being able to go all async using IdentityServer4 for signing access tokens.
Any status on this?
We have this issue as well where we need to do an http request to an HSM to sign access tokens. Our solution right now is to use WebClient.cs instead of the recommended way of doing http requests using HttpClient.cs. HttpClient does not support synchronous requests.
@mcthuesen We haven't moved forward much on this, but it is important for a number of reasons. We are not sure if this fits into our 5.x release or will need to wait for 6.0. Our 6.0 release fits with CORE 3.0.
Speaking on behalf of Azure DevOps, we'd love to have an async variant of the AudienceValidator
delegate.
Any idea when this will be available?
@LeroyK at the earliest in the 1st qtr of 2020 for a full stack. However if Async Delegates were available would that satisfy a pent up need?
That may be simpler and we could stage the deliverable. Most likely, we would put the additions into JsonWebTokenHandler.
@mfeingol @mcthuesen @bugproof @shagenhokie @dhjf @MicahZoltu
Looking for feedback.
Just came across this, was there any follow-up? I'm mostly interested in async versions of SignatureProvider.Sign
and SignatureProvider.Verify
@daniel-munch-cko we are working on shipping 6.x where we have a number of internal fixes being validated. Once that is out, we will address this as a lot of interest has been expressed. Particularly where crypto operations are performed out-of-process.
This would be extremely helpful for our case where we have an ecosystem of services that communicate with each others APIs using JWT tokens, so they need to fetch signing keys on demand as they are rotated.
@stebet that's exactly my usecase as well, it sucks having to do .Result
on async
method
@stebet that's exactly my usecase as well, it sucks having to do
.Result
onasync
method
Shhhh... you'll invoke @davidfowl....
@cyberhck @stebet @daniel-munch-cko yes we hear you. We have some internal work to do with our 6.x branch (our dev is ahead of dev5x). We do not want to backport this work, so the order of items is 6.x public, coordinating with asp.net, identityserver to ensure no breaking issues. Then we can address these long standing issues.
Kinda funny to see it open since 2016.
@bugproof fair enough. When opened the win was seen as a programming convenience, in a large number of cases, as almost all crypto operations were inproc and synchronous. That has changed as it is now common for part of the inbound validation for on out-of-proc or web request to be invoked.
Hello @brentschmaltz , Just wants to be sure that there isn't any new update regarding the async versions of SignatureProvider.Sign and SignatureProvider.Verify?
@Mordahlhuilhulh not yet ...
@brentschmaltz Thanks for your quick replay. Do you know when the async version is planned?
@Mordahlhuilhulh it looks like it's planned for V6 looking at assigned milestone.
This might help anyone looking to do async JWT validation.
I ended up adding a new interface that also implements ISecurityTokenValidator
. This allows me to add my custom validator to the JwtBearerOptions.TokenValidators
collection and in my version of JwtBearerHandler
(see #29917), I check to see if I'm working w/ the synchronous or asynchronous abstraction and act accordingly. The nice part about this approach is that it's a non-breaking change. The not so nice part is the potential for type checks, which I suppose could be hidden away behind an extension method, but still somewhat tacky.
public interface IAsyncSecurityTokenValidator : ISecurityTokenValidator
{
/// <summary>
/// Validates a token passed as a string using <see cref="TokenValidationParameters"/>
/// </summary>
ValueTask<(ClaimsPrincipal principal, SecurityToken validatedToken)> ValidateTokenAsync(
string securityToken,
TokenValidationParameters validationParameters
);
}
public class CustomAsyncSecurityTokenValidator : JwtSecurityTokenHandler, IAsyncSecurityTokenValidator
{
// optionally override the synchronous method
// NOTE: You shouldn't let this actually be called, but it's better that this is called and the real validation happens
// rather than running the default `JwtSecurityTokenHandler.ValidateToken` code. You'll need to add custom
// IAuthenticationHandler to call the async method appropriately - see `JwtBearerAsyncValidationHandler` in #29917
public override ClaimsPrincipal ValidateToken(
string token,
TokenValidationParameters validationParameters,
out SecurityToken validatedToken
)
{
ClaimsPrincipal principal;
(principal, validatedToken) = ValidateTokenAsync(token, validationParameters).AsTask().GetAwaiter().GetResult();
return principal;
}
public ValueTask<(ClaimsPrincipal principal, SecurityToken validatedToken)> ValidateTokenAsync(
string securityToken,
TokenValidationParameters validationParameters
)
{
// do async stuff here
}
}
This might help anyone looking to do async JWT validation...
This is a workaround that does semi-work, but the sync-over-async implementation is risky as it might cause threadpool starvation on a high-traffic implementation.
We really need a proper async implementation here, but thanks for this workaround :)
This might help anyone looking to do async JWT validation...
This is a workaround that does semi-work, but the sync-over-async implementation is risky as it might cause threadpool starvation on a high-traffic implementation.
We really need a proper async implementation here, but thanks for this workaround :)
If you look at the linked issue it also includes a custom authentication handler implementation that does a type check to see if the token validator is an IAsyncSecurityTokenValidator
, so it's async all the way down. There shouldn't be any issues w/ this unless I'm misunderstanding something.
EDIT: Turns out I put the wrong code in the linked issue. I've updated it to properly invoke the IAsyncSecurityTokenHandler
:
if (validator is IAsyncSecurityTokenValidator asyncValidator)
{
(principal, validatedToken) = await asyncValidator.ValidateTokenAsync(token, validationParameters).ConfigureAwait(false);
if (principal == null || validatedToken == null)
{
throw new SecurityTokenValidationException("The provided token failed validation.");
}
}
else
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}