Autoriser avec un schéma spécifique dans ASP.NET Core

Pour une présentation des schémas d’authentification dans ASP.NET Core, consultez Schéma d’authentification.

Dans certains scénarios, tels que les applications monopage (SPAs), il est courant d’utiliser plusieurs méthodes d’authentification. Par exemple, l’application peut utiliser cookiel’authentification basée sur la connexion et l’authentification du porteur JWT pour les requêtes JavaScript. Dans certains cas, l’application peut avoir plusieurs instances d’un gestionnaire d’authentification. Par exemple, deux cookie gestionnaires où l’un contient une identité de base et l’autre est créé lorsqu’une authentification multifacteur (MFA) a été déclenchée. L’authentification multifacteur peut être déclenchée parce que l’utilisateur a demandé une opération qui nécessite une sécurité supplémentaire. Pour plus d’informations sur l’application de l’authentification multifacteur lorsqu’un utilisateur demande une ressource qui nécessite l’authentification multifacteur, consultez la section Protection des problèmes GitHub avec MFA.

Un schéma d’authentification est nommé lorsque le service d’authentification est configuré pendant l’authentification. Par exemple :

using Microsoft.AspNetCore.Authentication;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication()
        .AddCookie(options =>
        {
            options.LoginPath = "/Account/Unauthorized/";
            options.AccessDeniedPath = "/Account/Forbidden/";
        })
        .AddJwtBearer(options =>
        {
            options.Audience = "http://localhost:5001/";
            options.Authority = "http://localhost:5000/";
        });

builder.Services.AddAuthentication()
        .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

Dans le code précédent, deux gestionnaires d’authentification ont été ajoutés : un pour cookies et un pour le porteur.

Notes

La spécification du schéma par défaut entraîne la définition de la propriété HttpContext.Usersur cette identité. Si ce comportement n’est pas souhaité, désactivez-le en appelant la forme sans paramètre de AddAuthentication.

Sélection du schéma avec l’attribut Autoriser

Au point d’autorisation, l’application indique le gestionnaire à utiliser. Sélectionnez le gestionnaire avec lequel l’application autorisera en transmettant une liste délimitée par des virgules de schémas d’authentification à [Authorize]. L’attribut [Authorize] spécifie le ou les schémas d’authentification à utiliser, qu’une valeur par défaut soit configurée ou non. Par exemple :

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc;

namespace AuthScheme.Controllers;

[Authorize(AuthenticationSchemes = AuthSchemes)]
public class MixedController : Controller
{
    private const string AuthSchemes =
        CookieAuthenticationDefaults.AuthenticationScheme + "," +
        JwtBearerDefaults.AuthenticationScheme;
    public ContentResult Index() => Content(MyWidgets.GetMyContent());

}

Dans l’exemple précédent, les gestionnaires et du porteur cookie s’exécutent et ont la possibilité de créer et d’ajouter une identité pour l’utilisateur actuel. En spécifiant un seul schéma, le gestionnaire correspondant exécute :

[Authorize(AuthenticationSchemes=JwtBearerDefaults.AuthenticationScheme)]
public class Mixed2Controller : Controller
{
    public ContentResult Index() => Content(MyWidgets.GetMyContent());
}

Dans le code précédent, seul le gestionnaire avec le schéma « Porteur » s’exécute. Toutes les identités basées cookie sont ignorées.

Sélection du schéma avec des stratégies

Si vous préférez spécifier les schémas souhaités dans la stratégie, vous pouvez définir la collection lors de l’ajout AuthenticationSchemes d’une stratégie :

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", policy =>
    {
        policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
        policy.RequireAuthenticatedUser();
        policy.Requirements.Add(new MinimumAgeRequirement(18));
    });
});

builder.Services.AddAuthentication()
                .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

Dans l’exemple précédent, la stratégie « Over18 » s’exécute uniquement sur l’identité créée par le gestionnaire « Porteur ». Utilisez la stratégie en définissant la propriété [Authorize]de l’attribut Policy :

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace AuthScheme.Controllers;
[Authorize(Policy = "Over18")]
public class RegistrationController : Controller
{
    // Do Registration

Utiliser plusieurs schémas d’authentification

Certaines applications peuvent avoir besoin de prendre en charge plusieurs types d’authentification. Par exemple, votre application peut authentifier des utilisateurs à partir d’Azure Active Directory et d’une base de données d’utilisateurs. Un autre exemple est une application qui authentifie les utilisateurs de Services ADFS et d’Azure Active Directory B2C. Dans ce cas, l’application doit accepter un jeton du porteur JWT de plusieurs émetteurs.

Ajoutez tous les schémas d’authentification que vous souhaitez accepter. Par exemple, le code suivant ajoute deux schémas d’authentification du porteur JWT avec différents émetteurs :

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;

var builder = WebApplication.CreateBuilder(args);

// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://localhost:5000/identity/";
        })
        .AddJwtBearer("AzureAD", options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://login.microsoftonline.com/eb971100-7f436/";
        });

// Authorization
builder.Services.AddAuthorization(options =>
{
    var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
        JwtBearerDefaults.AuthenticationScheme,
        "AzureAD");
    defaultAuthorizationPolicyBuilder =
        defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
    options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});

builder.Services.AddAuthentication()
        .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

Notes

Une seule authentification du porteur JWT est inscrite avec le schéma d’authentification par défaut JwtBearerDefaults.AuthenticationScheme. Une authentification supplémentaire doit être inscrite avec un schéma d’authentification unique.

Mettez à jour la stratégie d’autorisation par défaut pour accepter les deux schémas d’authentification. Par exemple :

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;

var builder = WebApplication.CreateBuilder(args);

// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://localhost:5000/identity/";
        })
        .AddJwtBearer("AzureAD", options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://login.microsoftonline.com/eb971100-7f436/";
        });

// Authorization
builder.Services.AddAuthorization(options =>
{
    var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
        JwtBearerDefaults.AuthenticationScheme,
        "AzureAD");
    defaultAuthorizationPolicyBuilder =
        defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
    options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});

builder.Services.AddAuthentication()
        .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

Comme la stratégie d’autorisation par défaut est remplacée, il est possible d’utiliser l’attribut dans les contrôleurs [Authorize]. Le contrôleur accepte ensuite les demandes avec JWT émises par le premier ou le deuxième émetteur.

Consultez ce problème GitHub sur l’utilisation de plusieurs schémas d’authentification.

L’exemple suivant utilise Azure Active Directory B2C et un autre locataire Azure Active Directory :

using Microsoft.AspNetCore.Authentication;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
using System.IdentityModel.Tokens.Jwt;

var builder = WebApplication.CreateBuilder(args);

// Authentication
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = "B2C_OR_AAD";
    options.DefaultChallengeScheme = "B2C_OR_AAD";
})
.AddJwtBearer("B2C", jwtOptions =>
{
    jwtOptions.MetadataAddress = "B2C-MetadataAddress";
    jwtOptions.Authority = "B2C-Authority";
    jwtOptions.Audience = "B2C-Audience";
})
.AddJwtBearer("AAD", jwtOptions =>
{
    jwtOptions.MetadataAddress = "AAD-MetadataAddress";
    jwtOptions.Authority = "AAD-Authority";
    jwtOptions.Audience = "AAD-Audience";
    jwtOptions.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        ValidAudiences = builder.Configuration.GetSection("ValidAudiences").Get<string[]>(),
        ValidIssuers = builder.Configuration.GetSection("ValidIssuers").Get<string[]>()
    };
})
.AddPolicyScheme("B2C_OR_AAD", "B2C_OR_AAD", options =>
{
    options.ForwardDefaultSelector = context =>
    {
        string authorization = context.Request.Headers[HeaderNames.Authorization];
        if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
        {
            var token = authorization.Substring("Bearer ".Length).Trim();
            var jwtHandler = new JwtSecurityTokenHandler();

            return (jwtHandler.CanReadToken(token) && jwtHandler.ReadJwtToken(token).Issuer.Equals("B2C-Authority"))
                ? "B2C" : "AAD";
        }
        return "AAD";
    };
});

builder.Services.AddAuthentication()
        .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute().RequireAuthorization();
app.MapRazorPages().RequireAuthorization();

app.MapFallbackToFile("index.html");

app.Run();

Dans le code précédent, ForwardDefaultSelector est utilisé pour sélectionner un schéma par défaut pour la demande actuelle à laquelle les gestionnaires d’authentification doivent transférer toutes les opérations d’authentification par défaut. La logique de transfert par défaut vérifie d’abord le paramètre le plus spécifique ForwardAuthenticate, ForwardChallenge, ForwardForbid, ForwardSignIn, etForwardSignOut, suivi de la vérification de ForwardDefaultSelector, suivi de ForwardDefault. Le premier résultat non null est utilisé comme schéma cible vers laquelle effectuer le transfert. Pour plus d’informations, consultez Schémas de politique dans ASP.NET Core.

Pour une présentation des schémas d’authentification dans ASP.NET Core, consultez Schéma d’authentification.

Dans certains scénarios, tels que les applications monopage (SPAs), il est courant d’utiliser plusieurs méthodes d’authentification. Par exemple, l’application peut utiliser cookiel’authentification basée sur la connexion et l’authentification du porteur JWT pour les requêtes JavaScript. Dans certains cas, l’application peut avoir plusieurs instances d’un gestionnaire d’authentification. Par exemple, deux cookie gestionnaires où l’un contient une identité de base et l’autre est créé lorsqu’une authentification multifacteur (MFA) a été déclenchée. L’authentification multifacteur peut être déclenchée parce que l’utilisateur a demandé une opération qui nécessite une sécurité supplémentaire. Pour plus d’informations sur l’application de l’authentification multifacteur lorsqu’un utilisateur demande une ressource qui nécessite l’authentification multifacteur, consultez la section Protection des problèmes GitHub avec MFA.

Un schéma d’authentification est nommé lorsque le service d’authentification est configuré pendant l’authentification. Par exemple :

public void ConfigureServices(IServiceCollection services)
{
    // Code omitted for brevity

    services.AddAuthentication()
        .AddCookie(options => {
            options.LoginPath = "/Account/Unauthorized/";
            options.AccessDeniedPath = "/Account/Forbidden/";
        })
        .AddJwtBearer(options => {
            options.Audience = "http://localhost:5001/";
            options.Authority = "http://localhost:5000/";
        });

Dans le code précédent, deux gestionnaires d’authentification ont été ajoutés : un pour cookies et un pour le porteur.

Notes

La spécification du schéma par défaut entraîne la définition de la propriété HttpContext.Usersur cette identité. Si ce comportement n’est pas souhaité, désactivez-le en appelant la forme sans paramètre de AddAuthentication.

Sélection du schéma avec l’attribut Autoriser

Au point d’autorisation, l’application indique le gestionnaire à utiliser. Sélectionnez le gestionnaire avec lequel l’application autorisera en transmettant une liste délimitée par des virgules de schémas d’authentification à [Authorize]. L’attribut [Authorize] spécifie le ou les schémas d’authentification à utiliser, qu’une valeur par défaut soit configurée ou non. Par exemple :

[Authorize(AuthenticationSchemes = AuthSchemes)]
public class MixedController : Controller
    // Requires the following imports:
    // using Microsoft.AspNetCore.Authentication.Cookies;
    // using Microsoft.AspNetCore.Authentication.JwtBearer;
    private const string AuthSchemes =
        CookieAuthenticationDefaults.AuthenticationScheme + "," +
        JwtBearerDefaults.AuthenticationScheme;

Dans l’exemple précédent, les gestionnaires et du porteur cookie s’exécutent et ont la possibilité de créer et d’ajouter une identité pour l’utilisateur actuel. En spécifiant un seul schéma, le gestionnaire correspondant exécute.

[Authorize(AuthenticationSchemes = 
    JwtBearerDefaults.AuthenticationScheme)]
public class MixedController : Controller

Dans le code précédent, seul le gestionnaire avec le schéma « Porteur » s’exécute. Toutes les identités basées cookie sont ignorées.

Sélection du schéma avec des stratégies

Si vous préférez spécifier les schémas souhaités dans la stratégie, vous pouvez définir la collection lors de l’ajout AuthenticationSchemes de votre stratégie :

services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", policy =>
    {
        policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
        policy.RequireAuthenticatedUser();
        policy.Requirements.Add(new MinimumAgeRequirement());
    });
});

Dans l’exemple précédent, la stratégie « Over18 » s’exécute uniquement sur l’identité créée par le gestionnaire « Porteur ». Utilisez la stratégie en définissant la propriété [Authorize]de l’attribut Policy :

[Authorize(Policy = "Over18")]
public class RegistrationController : Controller

Utiliser plusieurs schémas d’authentification

Certaines applications peuvent avoir besoin de prendre en charge plusieurs types d’authentification. Par exemple, votre application peut authentifier des utilisateurs à partir d’Azure Active Directory et d’une base de données d’utilisateurs. Un autre exemple est une application qui authentifie les utilisateurs de Services ADFS et d’Azure Active Directory B2C. Dans ce cas, l’application doit accepter un jeton du porteur JWT de plusieurs émetteurs.

Ajoutez tous les schémas d’authentification que vous souhaitez accepter. Par exemple, le code suivant dans Startup.ConfigureServices ajoute deux schémas d’authentification du porteur JWT avec différents émetteurs :

public void ConfigureServices(IServiceCollection services)
{
    // Code omitted for brevity

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://localhost:5000/identity/";
        })
        .AddJwtBearer("AzureAD", options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://login.microsoftonline.com/eb971100-6f99-4bdc-8611-1bc8edd7f436/";
        });
}

Notes

Une seule authentification du porteur JWT est inscrite avec le schéma d’authentification par défaut JwtBearerDefaults.AuthenticationScheme. Une authentification supplémentaire doit être inscrite avec un schéma d’authentification unique.

L’étape suivante consiste à mettre à jour la stratégie d’autorisation par défaut pour accepter les deux schémas d’authentification. Par exemple :

public void ConfigureServices(IServiceCollection services)
{
    // Code omitted for brevity

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

Comme la stratégie d’autorisation par défaut est remplacée, il est possible d’utiliser l’attribut dans les contrôleurs [Authorize]. Le contrôleur accepte ensuite les demandes avec JWT émises par le premier ou le deuxième émetteur.

Consultez ce problème GitHub sur l’utilisation de plusieurs schémas d’authentification.