web-dev-qa-db-fra.com

ASP.NET Core 2.0 combinant les cookies et l'autorisation de support pour le même point de terminaison

J'ai créé un nouveau projet d'application Web ASP.NET Core dans VS17 en utilisant le modèle "Application Web (Model-View-Controller)" et ".Net Framework" + "ASP.NET Core 2" comme configuration. La configuration d'authentification est définie sur "Comptes d'utilisateurs individuels".

J'ai l'exemple de point de terminaison suivant:

[Produces("application/json")]
[Route("api/price")]
[Authorize(Roles = "PriceViwer", AuthenticationSchemes = "Cookies,Bearer")]
public class PriceController : Controller
{

    public IActionResult Get()
    {
        return Ok(new Dictionary<string, string> { {"Galleon/Pound",
                                                   "999.999" } );
    }
}

"Cookies,Bearer" Est dérivé en concaténant CookieAuthenticationDefaults.AuthenticationScheme Et JwtBearerDefaults.AuthenticationScheme.

L'objectif est de pouvoir configurer l'autorisation pour le point final afin qu'il soit possible d'y accéder en utilisant à la fois les méthodes d'authentification par jeton et cookie.

Voici la configuration que j'ai pour l'authentification dans mon Startup.cs:

    services.AddAuthentication()
        .AddCookie(cfg => { cfg.SlidingExpiration = true;})
        .AddJwtBearer(cfg => {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
            cfg.TokenValidationParameters = new TokenValidationParameters() {
                                                    ValidIssuer = Configuration["Tokens:Issuer"],
                                                    ValidAudience = Configuration["Tokens:Issuer"],
                                                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
                                                };
        });

Ainsi, lorsque j'essaie d'accéder au point de terminaison à l'aide d'un navigateur, j'obtiens la réponse 401 avec une page html vierge.
I get the 401 response with a blank html page.

Ensuite, je me connecte et lorsque j'essaie d'accéder à nouveau au point de terminaison, j'obtiens la même réponse.

Ensuite, j'essaie d'accéder au point de terminaison en spécifiant le jeton du porteur. Et cela renvoie le résultat souhaité avec la réponse 200.
And that returns the desired result with the 200 response.

Donc, si je supprime [Authorize(AuthenticationSchemes = "Cookies,Bearer")], la situation devient l'inverse - l'authentification par cookie fonctionne et renvoie 200, mais la même méthode de jeton porteur que celle utilisée ci-dessus ne donne aucun résultat et redirige simplement vers la connexion AspIdentity par défaut page.

Je peux voir deux problèmes possibles ici:

1) ASP.NET Core n'autorise pas l'authentification "combinée". 2) "Cookies" n'est pas un nom de schéma valide. Mais alors quel est le bon à utiliser?

S'il vous plaît donnez votre avis. Je vous remercie.

18
maximiniini

Je pense que vous n'avez pas besoin de définir le système d'authentification sur votre contrôleur. Utilisez simplement l'utilisateur authentifié dans ConfigureServices comme ceci:

// requires: using Microsoft.AspNetCore.Authorization;
//           using Microsoft.AspNetCore.Mvc.Authorization;
services.AddMvc(config =>
{
    var policy = new AuthorizationPolicyBuilder()
                     .RequireAuthenticatedUser()
                     .Build();
    config.Filters.Add(new AuthorizeFilter(policy));
});

Pour la documentation de mes sources: registerAuthorizationHandlers

Pour la partie, si le schéma-Key n'était pas valide, vous pouvez utiliser une chaîne interpolée, pour utiliser les bonnes clés:

[Authorize(AuthenticationSchemes = $"{CookieAuthenticationDefaults.AuthenticationScheme},{JwtBearerDefaults.AuthenticationScheme}")]

Edit: J'ai fait d'autres recherches et suis arrivé à la conclusion suivante: il n'est pas possible d'autoriser une méthode avec deux schémas Or-Like, mais vous pouvez utiliser deux méthodes publiques, pour appeler une méthode privée comme celle-ci:

//private method
private IActionResult GetThingPrivate()
{
   //your Code here
}

//Jwt-Method
[Authorize(AuthenticationSchemes = $"{JwtBearerDefaults.AuthenticationScheme}")]
[HttpGet("bearer")]
public IActionResult GetByBearer()
{
   return GetThingsPrivate();
}

 //Cookie-Method
[Authorize(AuthenticationSchemes = $"{CookieAuthenticationDefaults.AuthenticationScheme}")]
[HttpGet("cookie")]
public IActionResult GetByCookie()
{
   return GetThingsPrivate();
}
10
Nikolaus

Si je comprends bien la question, je pense qu'il existe une solution. Dans l'exemple suivant, j'utilise l'authentification par cookie ET au porteur dans une seule application. L'attribut [Authorize] Peut être utilisé sans spécifier le schéma, et l'application réagira dynamiquement, selon la méthode d'autorisation utilisée.

services.AddAuthentication Est appelé deux fois pour enregistrer les 2 schémas d'authentification. La clé de la solution est l'appel à services.AddAuthorization À la fin de l'extrait de code, qui indique à ASP.NET d'utiliser les DEUX schémas .

J'ai testé cela et cela semble bien fonctionner.

(Basé sur Microsoft docs .)

services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = "oidc";
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddOpenIdConnect("oidc", options =>
    {
        options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.Authority = "https://localhost:4991";
        options.RequireHttpsMetadata = false;

        options.ClientId = "WebApp";
        options.ClientSecret = "secret";

        options.ResponseType = "code id_token";
        options.Scope.Add("api");
        options.SaveTokens = true;
    });

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://localhost:4991";
        options.RequireHttpsMetadata = false;
        // name of the API resource
        options.Audience = "api";
    });

services.AddAuthorization(options =>
{
    var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
        CookieAuthenticationDefaults.AuthenticationScheme,
        JwtBearerDefaults.AuthenticationScheme);
    defaultAuthorizationPolicyBuilder =
        defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
    options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});

[~ # ~] modifier [~ # ~]

Cela fonctionne pour les utilisateurs authentifiés, mais renvoie simplement un 401 (non autorisé) si un utilisateur ne s'est pas encore connecté.

Pour vous assurer que les utilisateurs non autorisés sont redirigés vers la page de connexion, ajoutez le code suivant à la méthode Configure dans votre classe de démarrage. Remarque: il est essentiel que le nouveau middleware soit placé après l'appel de la app.UseAuthentication().

app.UseAuthentication();
app.Use(async (context, next) =>
{
    await next();
    var bearerAuth = context.Request.Headers["Authorization"]
        .FirstOrDefault()?.StartsWith("Bearer ") ?? false;
    if (context.Response.StatusCode == 401
        && !context.User.Identity.IsAuthenticated
        && !bearerAuth)
    {
        await context.ChallengeAsync("oidc");
    }
});

Si vous connaissez un moyen plus propre de réaliser cette redirection, veuillez poster un commentaire!

3
David Kirkland