Autorización con un esquema específico en ASP.NET Core

Para obtener una introducción a los esquemas de autenticación en ASP.NET Core, consulte Esquema de autenticación.

En algunos escenarios, como aplicaciones de página única (SPA), es habitual usar varios métodos de autenticación. Por ejemplo, la aplicación puede usar la autenticación basada en cookie para iniciar sesión y la autenticación de portador JWT para las solicitudes de JavaScript. En algunos casos, la aplicación puede tener varias instancias de un controlador de autenticación. Por ejemplo, dos controladores de cookie en los que uno contiene una identidad básica y otro se crea cuando se ha desencadenado una autenticación multifactor (MFA). MFA se puede desencadenar porque el usuario solicitó una operación que requiere seguridad adicional. Para más información sobre la aplicación de MFA cuando un usuario solicita un recurso que requiere MFA, consulte el tema de GitHub Proteger la sección con MFA.

Un esquema de autenticación se determina cuando el servicio de autenticación se configura durante la autenticación. Por ejemplo:

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

En el código anterior, se han agregado dos controladores de autenticación: uno para cookies y otro para portador.

Nota

Especificar el esquema predeterminado da como resultado que la propiedad HttpContext.User se establezca con esa identidad. Si no se desea ese comportamiento, deshabilítelo invocando la forma sin parámetros de AddAuthentication.

Selección del esquema con el atributo Authorize

En el momento de la autorización, la aplicación indica el controlador que se va a usar. Seleccione el controlador con el que se autorizará la aplicación pasando una lista delimitada por comas de esquemas de autenticación a [Authorize]. El atributo [Authorize] especifica el esquema o esquemas de autenticación a usar independientemente de si se ha configurado un valor predeterminado. Por ejemplo:

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

}

En el ejemplo anterior, tanto el controlador de cookie como el de portador se ejecutan y tienen la oportunidad de crear y añadir una identidad para el usuario actual. Al especificar un solo esquema, el controlador correspondiente se ejecuta:

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

En el código anterior, solo se ejecuta el controlador con el esquema "Portador". Las identidades basadas en cookie se ignoran.

Selección del esquema con directivas

Si prefiere especificar los esquemas deseados en la directiva, puede establecer la colección AuthenticationSchemes al agregar una directiva:

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

En el ejemplo anterior, la directiva "Over18" solo se ejecuta en la identidad creada por el controlador de "Portador". Use la directiva estableciendo la propiedad Policy del atributo [Authorize]:

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

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

Uso de varios esquemas de autenticación

Es posible que algunas aplicaciones necesiten admitir varios tipos de autenticación. Por ejemplo, la aplicación podría autenticar usuarios de Azure Active Directory y de una base de datos de usuarios. Otro ejemplo es una aplicación que autentica a los usuarios tanto de Servicios de federación de Active Directory (AD FS) como de Azure Active Directory B2C. En este caso, la aplicación debe aceptar un token de portador JWT de varios emisores.

Agregue todos los esquemas de autenticación que quiere aceptar. Por ejemplo, el código siguiente agrega dos esquemas de autenticación de portador JWT con distintos emisores:

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

Nota

Solo se registra una autenticación de portador JWT con el esquema JwtBearerDefaults.AuthenticationScheme de autenticación predeterminado. La autenticación adicional debe registrarse con un esquema de autenticación único.

Actualice la directiva de autorización predeterminada para aceptar ambos esquemas de autenticación. Por ejemplo:

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

Como la directiva de autorización predeterminada se invalida, es posible usar el atributo [Authorize] en los controladores. A continuación, el controlador acepta solicitudes con JWT emitido por el primer o segundo emisor.

Consulte este problema de GitHub sobre el uso de varios esquemas de autenticación.

En el ejemplo siguiente se usa Azure Active Directory B2C y otro inquilino de 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();

En el código anterior, ForwardDefaultSelector se utiliza para seleccionar un esquema predeterminado para la solicitud actual al que los controladores de autenticación deberían reenviar todas las operaciones de autenticación de forma predeterminada. La lógica de reenvío predeterminada comprueba primero la configuración más específica ForwardAuthenticate, ForwardChallenge, ForwardForbid, ForwardSignIn y ForwardSignOut, seguida de la comprobación de ForwardDefaultSelector, seguida de ForwardDefault. El primer resultado distinto de NULL se usa como esquema de destino al que se reenvía. Para más información, consulte Esquemas de directivas en ASP.NET Core.

Para obtener una introducción a los esquemas de autenticación en ASP.NET Core, consulte Esquema de autenticación.

En algunos escenarios, como aplicaciones de página única (SPA), es habitual usar varios métodos de autenticación. Por ejemplo, la aplicación puede usar la autenticación basada en cookie para iniciar sesión y la autenticación de portador JWT para las solicitudes de JavaScript. En algunos casos, la aplicación puede tener varias instancias de un controlador de autenticación. Por ejemplo, dos controladores de cookie en los que uno contiene una identidad básica y otro se crea cuando se ha desencadenado una autenticación multifactor (MFA). MFA se puede desencadenar porque el usuario solicitó una operación que requiere seguridad adicional. Para más información sobre la aplicación de MFA cuando un usuario solicita un recurso que requiere MFA, consulte el tema de GitHub Proteger la sección con MFA.

Un esquema de autenticación se determina cuando el servicio de autenticación se configura durante la autenticación. Por ejemplo:

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

En el código anterior, se han agregado dos controladores de autenticación: uno para cookies y otro para portador.

Nota

Especificar el esquema predeterminado da como resultado que la propiedad HttpContext.User se establezca con esa identidad. Si no se desea ese comportamiento, deshabilítelo invocando la forma sin parámetros de AddAuthentication.

Selección del esquema con el atributo Authorize

En el momento de la autorización, la aplicación indica el controlador que se va a usar. Seleccione el controlador con el que se autorizará la aplicación pasando una lista delimitada por comas de esquemas de autenticación a [Authorize]. El atributo [Authorize] especifica el esquema o esquemas de autenticación a usar independientemente de si se ha configurado un valor predeterminado. Por ejemplo:

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

En el ejemplo anterior, tanto el controlador de cookie como el de portador se ejecutan y tienen la oportunidad de crear y añadir una identidad para el usuario actual. Al especificar un solo esquema, se ejecuta el controlador correspondiente.

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

En el código anterior, solo se ejecuta el controlador con el esquema "Portador". Las identidades basadas en cookie se ignoran.

Selección del esquema con directivas

Si prefiere especificar los esquemas deseados en la directiva, puede establecer la colección AuthenticationSchemes al añadir su directiva:

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

En el ejemplo anterior, la directiva "Over18" solo se ejecuta en la identidad creada por el controlador de "Portador". Use la directiva estableciendo la propiedad Policy del atributo [Authorize]:

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

Uso de varios esquemas de autenticación

Es posible que algunas aplicaciones necesiten admitir varios tipos de autenticación. Por ejemplo, la aplicación podría autenticar usuarios de Azure Active Directory y de una base de datos de usuarios. Otro ejemplo es una aplicación que autentica a los usuarios tanto de Servicios de federación de Active Directory (AD FS) como de Azure Active Directory B2C. En este caso, la aplicación debe aceptar un token de portador JWT de varios emisores.

Agregue todos los esquemas de autenticación que quiere aceptar. Por ejemplo, el código siguiente en Startup.ConfigureServices agrega dos esquemas de autenticación de portador JWT con distintos emisores:

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

Nota

Solo se registra una autenticación de portador JWT con el esquema JwtBearerDefaults.AuthenticationScheme de autenticación predeterminado. La autenticación adicional debe registrarse con un esquema de autenticación único.

El siguiente paso es actualizar la directiva de autorización predeterminada para aceptar ambos esquemas de autenticación. Por ejemplo:

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

Como la directiva de autorización predeterminada se invalida, es posible usar el atributo [Authorize] en los controladores. A continuación, el controlador acepta solicitudes con JWT emitido por el primer o segundo emisor.

Consulte este problema de GitHub sobre el uso de varios esquemas de autenticación.