Użyj uwierzytelniania wielu nośników JWT

87

Czy można obsługiwać wielu wystawców tokenów JWT w ASP.NET Core 2? Chcę zapewnić interfejs API dla usługi zewnętrznej i muszę korzystać z dwóch źródeł tokenów JWT - Firebase i niestandardowych wydawców tokenów JWT. W ASP.NET core mogę ustawić uwierzytelnianie JWT dla schematu autoryzacji okaziciela, ale tylko dla jednego organu:

  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
                };
        }

Mogę mieć wielu wydawców i odbiorców, ale nie mogę ustawić kilku organów.

Rozsądny
źródło
1
AFAIK, możesz dodać dowolną liczbę właściwości do tokena JWT. Tak więc nic nie stoi na przeszkodzie, aby zapisać dwie nazwy emitentów w tokenach JWT. Problem polega na tym, że aplikacja musiałaby znać oba klucze, gdyby każdy wystawca używał innego klucza do podpisania.
Tim Biegeleisen,

Odpowiedzi:

186

Możesz całkowicie osiągnąć to, czego chcesz:

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();
    });

Przyjrzyjmy się różnicom między twoim kodem a tym.

AddAuthentication nie ma parametru

Jeśli ustawisz domyślny schemat uwierzytelniania, przy każdym żądaniu oprogramowanie pośredniczące uwierzytelniania spróbuje uruchomić procedurę obsługi uwierzytelniania skojarzoną z domyślnym schematem uwierzytelniania. Ponieważ mamy teraz dwa możliwe schematy uwierzytelniania, nie ma sensu uruchamiać jednego z nich.

Użyj innego przeciążenia AddJwtBearer

Każda AddXXXmetoda dodawania uwierzytelniania ma kilka przeciążeń:

Teraz, ponieważ dwukrotnie używasz tej samej metody uwierzytelniania, ale schematy uwierzytelniania muszą być unikatowe, musisz użyć drugiego przeciążenia.

Zaktualizuj zasady domyślne

Ponieważ żądania nie będą już automatycznie uwierzytelniane, nadanie [Authorize]atrybutów niektórym akcjom spowoduje odrzucenie żądań i wystawienie HTTP 401ich.

Ponieważ nie jest to, co chcemy, ponieważ chcemy dać przewodnicy uwierzytelnianie szansę uwierzytelnienia żądania, możemy zmienić domyślną politykę autoryzacji systemu, wskazując zarówno Firebasei Customuwierzytelniania schematy powinny być próbował uwierzytelnić prośbę.

To nie przeszkadza ci być bardziej restrykcyjnymi w niektórych działaniach; [Authorize]atrybut ma AuthenticationSchemeswłaściwość, która pozwala zastąpić systemy uwierzytelniania, które są ważne.

Jeśli masz bardziej złożone scenariusze, możesz skorzystać z autoryzacji opartej na zasadach . Uważam, że oficjalna dokumentacja jest świetna.

Wyobraźmy sobie, że niektóre akcje są dostępne tylko dla tokenów JWT wydanych przez Firebase i muszą mieć roszczenie o określonej wartości; możesz to zrobić w ten sposób:

// 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());
    });

Możesz następnie użyć [Authorize(Policy = "FirebaseAdministrators")]do niektórych działań.

Ostatnia uwaga: jeśli przechwytujesz AuthenticationFailedzdarzenia i używasz czegoś innego niż pierwsza AddJwtBearerzasada, możesz zobaczyć. IDX10501: Signature validation failed. Unable to match key...Jest to spowodowane tym, że system sprawdza każdą AddJwtBearerz nich po kolei, aż uzyska dopasowanie. Błąd można zwykle zignorować.

Mickaël Derriey
źródło
4
Czy to wymaga zmiany wartości nagłówka z Firebase lub niestandardowego rozwiązania? to znaczy. zamiast Authorization : Bearer <token>tego nagłówek będzie Authorization : Firebase <token>na przykład? Kiedy wypróbowałem to rozwiązanie, otrzymałem błąd: „Żaden program obsługi uwierzytelniania nie jest zarejestrowany dla schematu 'Nośnik'.”
Rush Frisby
4
Nie, nagłówki nie muszą się zmieniać. Komunikat o błędzie sugeruje, że odnosisz się do nieistniejącego schematu uwierzytelniania (nośnika). W naszych przykładach dwa zarejestrowane schematy to Firebase i Custom, które są pierwszymi argumentami .AddJwtBearerwywołań metod.
Mickaël Derriey
5
Cześć. Szukałem właśnie takiego rozwiązania. Niestety otrzymuję wyjątek „Nie określono schematu uwierzytelniania i nie znaleziono żadnego DefaultChallengeScheme”. options.DefaultPolicy jest ustawiona w porządku. Jakieś pomysły?
terjetyl
11
To była niezwykle pomocna odpowiedź i zebrałem wiele z tego, co widziałem w kawałkach w każdym miejscu.
Aron W.
2
@TylerOhlsen to nieprawda; chociaż będzie on używany w opisywanym przypadku, nie jest jedyny. Będzie również używany, jeśli nie określisz wymagania autoryzacji na poziomie punktu końcowego, ale ozdobisz kontrolery MVC i / lub akcje pustym [Authorize]atrybutem.
Mickaël Derriey
4

To jest rozszerzenie odpowiedzi Mickaëla Derrieya.

Nasza aplikacja ma niestandardowy wymóg autoryzacji, który rozwiązujemy z wewnętrznego źródła. Używaliśmy Auth0, ale przechodzimy na uwierzytelnianie konta Microsoft przy użyciu OpenID. Oto nieco zmieniony kod z naszego uruchomienia ASP.Net Core 2.1. Dla przyszłych czytelników działa to od chwili pisania dla określonych wersji. Obiekt wywołujący używa id_token z OpenID w przychodzących żądaniach przekazywanych jako token okaziciela. Mam nadzieję, że pomoże to komuś innemu, próbującemu dokonać konwersji autorytetu tożsamości, tak samo jak to pytanie i odpowiedź pomogły mi.

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());
});
Brak zwrotów Brak zwrotów
źródło