Utiliser plusieurs authentifications de support JWT

86

Est-il possible de prendre en charge plusieurs émetteurs de jetons JWT dans ASP.NET Core 2? Je souhaite fournir une API pour un service externe et je dois utiliser deux sources de jetons JWT - Firebase et les émetteurs de jetons JWT personnalisés. Dans ASP.NET core, je peux définir l'authentification JWT pour le schéma d'authentification du porteur, mais uniquement pour une autorité:

  services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Authority = "https://securetoken.google.com/my-firebase-project"
            options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = "my-firebase-project"
                    ValidateAudience = true,
                    ValidAudience = "my-firebase-project"
                    ValidateLifetime = true
                };
        }

Je peux avoir plusieurs émetteurs et publics, mais je ne peux pas définir plusieurs autorités.

Sain
la source
1
AFAIK vous pouvez ajouter n'importe quel nombre de propriétés à un JWT. Rien ne vous empêche donc d'enregistrer deux noms d'émetteurs dans un JWT. Le problème vient du fait que votre application aurait besoin de connaître les deux clés, si chaque émetteur utilisait une clé différente pour signer.
Tim Biegeleisen

Réponses:

186

Vous pouvez totalement réaliser ce que vous voulez:

services
    .AddAuthentication()
    .AddJwtBearer("Firebase", options =>
    {
        options.Authority = "https://securetoken.google.com/my-firebase-project"
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = "my-firebase-project"
            ValidateAudience = true,
            ValidAudience = "my-firebase-project"
            ValidateLifetime = true
        };
    })
    .AddJwtBearer("Custom", options =>
    {
        // Configuration for your custom
        // JWT tokens here
    });

services
    .AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes("Firebase", "Custom")
            .Build();
    });

Passons en revue les différences entre votre code et celui-là.

AddAuthentication n'a pas de paramètre

Si vous définissez un schéma d'authentification par défaut, à chaque demande, le middleware d'authentification essaiera d'exécuter le gestionnaire d'authentification associé au schéma d'authentification par défaut. Puisque nous avons maintenant deux schémas d'authentification opssibles, il est inutile d'en exécuter un.

Utilisez une autre surcharge de AddJwtBearer

Chaque AddXXXméthode unique pour ajouter une authentification a plusieurs surcharges:

Maintenant, comme vous utilisez la même méthode d'authentification deux fois mais que les schémas d'authentification doivent être uniques, vous devez utiliser la deuxième surcharge.

Mettre à jour la politique par défaut

Étant donné que les demandes ne seront plus authentifiées automatiquement, le fait de placer des [Authorize]attributs sur certaines actions entraînera le rejet des demandes et un HTTP 401sera émis.

Puisque ce n'est pas ce que nous voulons parce que nous voulons donner aux gestionnaires d'authentification une chance d'authentifier la demande, nous changeons la politique par défaut du système d'autorisation en indiquant à la fois les schémas d'authentification Firebaseet Customdoivent être essayés pour authentifier la demande.

Cela ne vous empêche pas d'être plus restrictif sur certaines actions; l' [Authorize]attribut a une AuthenticationSchemespropriété qui vous permet de remplacer les schémas d'authentification valides.

Si vous avez des scénarios plus complexes, vous pouvez utiliser l' autorisation basée sur des stratégies . Je trouve que la documentation officielle est excellente.

Imaginons que certaines actions ne soient disponibles que pour les jetons JWT émis par Firebase et doivent avoir une revendication avec une valeur spécifique; vous pouvez le faire de cette façon:

// Authentication code omitted for brevity

services
    .AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes("Firebase", "Custom")
            .Build();

        options.AddPolicy("FirebaseAdministrators", new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes("Firebase")
            .RequireClaim("role", "admin")
            .Build());
    });

Vous pouvez ensuite utiliser [Authorize(Policy = "FirebaseAdministrators")]sur certaines actions.

Un dernier point à noter: si vous attrapez des AuthenticationFailedévénements et n'utilisez rien d'autre que la première AddJwtBearerstratégie, vous pouvez voir que IDX10501: Signature validation failed. Unable to match key...cela est dû au fait que le système vérifie chacun AddJwtBearerà son tour jusqu'à ce qu'il obtienne une correspondance. L'erreur peut généralement être ignorée.

Mickaël Derriey
la source
4
Cela nécessite-t-il que la valeur d'en-tête soit modifiée par rapport à Firebase ou à une solution personnalisée? c'est à dire. au lieu de Authorization : Bearer <token>cela, l'en-tête être Authorization : Firebase <token>par exemple? Lorsque j'ai essayé cette solution, j'ai eu l'erreur: "Aucun gestionnaire d'authentification n'est enregistré pour le schéma 'Bearer'."
Rush Frisby
4
Non, les en-têtes n'ont pas besoin de changer. Le message d'erreur suggère que vous faites référence à un schéma d'authentification inexistant (Bearer). Dans nos exemples, les deux schémas enregistrés sont Firebase et Custom, qui sont les premiers arguments des .AddJwtBearerappels de méthode.
Mickaël Derriey
5
Salut. Je cherchais juste cette solution. Malheureusement, j'obtiens une exception "Aucun authenticationScheme n'a été spécifié, et il n'y avait pas de DefaultChallengeScheme trouvé". options.DefaultPolicy est défini sur ok. Des idées?
terjetyl
11
C'était une réponse extrêmement utile, et a rassemblé beaucoup de ce que j'ai vu en morceaux partout.
Aron W.
2
@TylerOhlsen ce n'est pas correct; bien qu'il soit utilisé dans le cas que vous décrivez, ce n'est pas le seul. Il sera également utilisé si vous ne spécifiez pas d'exigence d'autorisation au niveau du noeud final, mais que vous décorez les contrôleurs MVC et / ou les actions avec un [Authorize]attribut vide .
Mickaël Derriey le
4

C'est une extension de la réponse de Mickaël Derriey.

Notre application a une exigence d'autorisation personnalisée que nous résolvons à partir d'une source interne. Nous utilisions Auth0 mais passons à l'authentification de compte Microsoft à l'aide d'OpenID. Voici le code légèrement modifié de notre démarrage ASP.Net Core 2.1. Pour les futurs lecteurs, cela fonctionne dès la rédaction de cet article pour les versions spécifiées. L'appelant utilise l'id_token d'OpenID sur les demandes entrantes passées en tant que jeton Bearer. J'espère que cela aide quelqu'un d'autre à essayer de faire une conversion d'autorité d'identité autant que cette question et cette réponse m'ont aidé.

const string Auth0 = nameof(Auth0);
const string MsaOpenId = nameof(MsaOpenId);

string domain = "https://myAuth0App.auth0.com/";
services.AddAuthentication()
        .AddJwtBearer(Auth0, options =>
            {
                options.Authority = domain;
                options.Audience = "https://myAuth0Audience.com";
            })
        .AddJwtBearer(MsaOpenId, options =>
            {
                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    ValidateAudience = true,
                    ValidAudience = "00000000-0000-0000-0000-000000000000",

                    ValidateIssuer = true,
                    ValidIssuer = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0",

                    ValidateIssuerSigningKey = true,
                    RequireExpirationTime = true,
                    ValidateLifetime = true,
                    RequireSignedTokens = true,
                    ClockSkew = TimeSpan.FromMinutes(10),
                };
                options.MetadataAddress = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0/.well-known/openid-configuration";
            }
        );

services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes( Auth0, MsaOpenId )
        .Build();

    var approvedPolicyBuilder =  new AuthorizationPolicyBuilder()
           .RequireAuthenticatedUser()
           .AddAuthenticationSchemes(Auth0, MsaOpenId)
           ;

    approvedPolicyBuilder.Requirements.Add(new HasApprovedRequirement(domain));

    options.AddPolicy("approved", approvedPolicyBuilder.Build());
});
Aucun remboursement Aucun retour
la source