web-dev-qa-db-fra.com

JwtBearerEvents.OnMessageReceived non appelé pour le premier appel d'opération

J'utilise WSO2 comme fournisseur d'identité (IDP). Il met le JWT dans un en-tête appelé "X-JWT-Assertion".

Pour alimenter cela dans le système ASP.NET Core, j'ai ajouté un événement OnMessageReceived. Cela me permet de définir le token sur la valeur fournie dans l'en-tête.

Voici le code que je dois faire (la partie clé est les 3 dernières lignes de code sans parenthèse):

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddJwtBearer(async options =>
{
    options.TokenValidationParameters = 
         await wso2Actions.JwtOperations.GetTokenValidationParameters();

    options.Events = new JwtBearerEvents()
    {
        // WSO2 sends the JWT in a different field than what is expected.
        // This allows us to feed it in.
        OnMessageReceived = context =>
        {
            context.Token = context.HttpContext.Request.Headers["X-JWT-Assertion"];
            return Task.CompletedTask;
        }
    }
};

Tout cela fonctionne parfaitement sauf pour le tout premier appel après le démarrage du service. Pour être clair, chaque appel, sauf le premier, fonctionne exactement comme je le souhaite. (Il place le jeton et met à jour l'objet User comme j'en ai besoin.)

Mais pour le premier appel, le OnMessageReceived n'est pas touché. Et l'objet User dans mon contrôleur n'est pas configuré.

J'ai vérifié HttpContext pour ce premier appel, et l'en-tête "X-JWT-Assertion" est dans le Request.Headers list (avec le JWT dedans). Mais, pour une raison quelconque, l'événement OnMessageReceived n'est pas appelé pour cela.

Comment puis-je appeler OnMessageReceived lors de la première invocation d'une opération de service pour mon service?

NOTE IMPORTANTE: J'ai compris que le problème était asyncawait dans AddJwtBearer. (Voir ma réponse ci-dessous.) C'est ce que je voulais vraiment de cette question.

Cependant, comme une prime ne peut pas être annulée, je vais quand même attribuer la prime à quiconque peut montrer une façon d'utiliser AddJwtBearer avec asyncawait là où elle est en attente un appel réel HttpClient. Ou montrez la documentation expliquant pourquoi asyncawait n'est pas censé être utilisé avec AddJwtBearer.

8
Vaccano

Vous pouvez utiliser GetAwaiter().GetResult() pour exécuter du code asynchrone au démarrage. Cela bloquera le thread, mais ce n'est pas grave car il ne s'exécute qu'une seule fois et il est au démarrage de l'application.

Cependant, si vous n'aimez pas bloquer le thread et insistez pour utiliser await pour obtenir les options, vous pouvez utiliser asyncawait dans Program.cs pour obtenir vos options et les stocker dans une classe statique et l'utiliser au démarrage.

public class Program
{
    public static async Task Main(string[] args)
    {
        JwtParameter.TokenValidationParameters = await wso2Actions.JwtOperations.GetTokenValidationParameters();
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

public static class JwtParameter
{
    public static TokenValidationParameters TokenValidationParameters { get; set; }
}
0
Kahbazi

La raison pour laquelle vos deux premières requêtes ne peuvent pas déclencher OnMessageReceived n'est pas à cause de async void délégué que vous utilisez, mais l'ordre de chargement des paramètres et des événements attachés.

Vous attachez des gestionnaires aux événements aprèsawait, ce qui signifie que vous avez créé une condition de concurrence ici, que, si disons qu'une requête arrive avant que await soit terminée, il n'y a pas d'événement gestionnaire attaché à OnMessageReceived du tout.

Pour résoudre ce problème, vous devez attacher des gestionnaires d'événements avant le premier await. Cela garantira que vous aurez toujours des gestionnaires d'événements attachés à OnMessageReceived.

Essayez ce code:

services.AddAuthentication(opt =>
    {
        // ...
    })
    .AddJwtBearer(async opt =>
    {
        var tcs = new TaskCompletionSource<object>();

        // Any code before the first await in this delegate can run
        // synchronously, so if you have events to attach for all requests
        // attach handlers before await.
        opt.Events = new JwtBearerEvents
        {
            // This method is first event in authentication pipeline
            // we have chance to wait until TokenValidationParameters
            // is loaded.
            OnMessageReceived = async context =>
            {
                // Wait until token validation parameters loaded.
                await tcs.Task;
            }
        };

        // This delegate returns if GetTokenValidationParametersAsync
        // does not complete synchronously 
        try
        {
            opt.TokenValidationParameters = await GetTokenValidationParametersAsync();
        }
        finally
        {
            tcs.TrySetResult(true);
        }

        // Any code here will be executed as continuation of
        // GetTokenValidationParametersAsync and may not 
        // be seen by first couple requests
    });
0
weichch