ASP.NET Core で特定のスキームを使用して認可する

ASP.NET Core の認証スキームの概要については、「認証スキーム」を参照してください。

シングルページ アプリケーション (SPA) などの一部のシナリオでは、複数の認証方法を使うのが一般的です。 たとえば、ログインには cookie ベースの認証を使い、JavaScript 要求には JWT ベアラー認証を使うアプリがあります。 場合によっては、複数インスタンスの認証ハンドラーを持つアプリもあります。 たとえば、2 つの cookie ハンドラーがあり、1 つは基本 ID を含み、1 つは多要素認証 (MFA) がトリガーされたときに作成されます。 MFA がトリガーされるのは、追加のセキュリティを必要とする操作をユーザーが要求した場合などです。 MFA を必要とするリソースをユーザーが要求したときに MFA を実施する方法については、GitHub の問題「Protect section with MFA (MFA を使ってセクションを保護する)」を参照してください。

認証スキームは、認証中に認証サービスを構成するときに名前が付けられます。 次に例を示します。

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

先ほどのコードでは、cookie 用とベアラー用の 2 つの認証ハンドラーを追加しました。

注意

既定のスキームを指定すると、HttpContext.User プロパティにその ID が設定されます。 この動作が好ましくない場合は、AddAuthentication のパラメーターなしのフォームを呼び出して無効にします。

Authorize 属性を使ったスキームの選択

認可の時点で、使われるハンドラーがアプリから示されます。 認証スキームのコンマ区切りリストを [Authorize] に渡すことで、アプリに認可されるハンドラーを選びます。 既定値が構成されているかどうかにかかわらず、使う認証スキームを 1 つ以上 [Authorize] 属性に指定します。 次に例を示します。

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

}

前の例では、cookie とベアラーの両方のハンドラーが実行されるので、現在のユーザーの ID を作成して追加する機会があります。 1 つのスキームのみを指定すると、対応するハンドラーが実行されます。

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

前のコードでは、"ベアラー" スキームのハンドラーのみが実行されます。 cookie ベースの ID はすべて無視されます。

ポリシーを使ったスキームの選択

ポリシーで目的のスキームを指定したい場合は、ポリシーの追加時に AuthenticationSchemes コレクションを設定できます。

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

前の例では、"ベアラー" ハンドラーによって作成された ID に対してのみ、"Over18" ポリシーが実行されます。 ポリシーを使うには、[Authorize] 属性の Policy プロパティを設定します。

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

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

複数の認証スキームを使う

アプリによっては、複数の種類の認証をサポートする必要があります。 たとえば、アプリで Azure Active Directory とユーザー データベースのユーザーを認証する場合があります。 また、Active Directory フェデレーション サービスと Azure Active Directory B2C の両方のユーザーを認証するアプリの例もあります。 この場合、アプリは複数の発行元からの JWT ベアラー トークンを受け取る必要があります。

受け取るすべての認証スキームを追加してください。 たとえば、次のコードでは、発行元の異なる 2 つの JWT ベアラー認証スキームを追加しています。

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

注意

既定の認証スキーム JwtBearerDefaults.AuthenticationScheme には、1 つの JWT ベアラー認証のみが登録されています。 追加の認証は、固有の認証スキームに登録する必要があります。

両方の認証スキームを受け取るように既定の認可ポリシーを更新します。 次に例を示します。

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

既定の認可ポリシーがオーバーライドされたので、コントローラーで [Authorize] 属性を使うことができます。 これで、コントローラーは、最初または 2 つ目の発行元から発行された JWT を使った要求を受け取るようになります。

複数の認証スキームの使用に関するこの GitHub の問題を参照してください。

次の例では、Azure Active Directory B2C と別の 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();

上記のコードでは、ForwardDefaultSelector を使い、既定で認証ハンドラーがすべての認証操作を転送するという現在の要求の既定スキームを選んでいます。 既定の転送ロジックでは、最も具体的な ForwardAuthenticateForwardChallengeForwardForbidForwardSignInForwardSignOut の設定を最初に確認し、次に ForwardDefaultSelectorForwardDefault の順に確認します。 最初に null ではない結果が転送先のターゲット スキームとして使われます。 詳細については、「ASP.NET Core のポリシー スキーム」を参照してください。

ASP.NET Core の認証スキームの概要については、「認証スキーム」を参照してください。

シングルページ アプリケーション (SPA) などの一部のシナリオでは、複数の認証方法を使うのが一般的です。 たとえば、ログインには cookie ベースの認証を使い、JavaScript 要求には JWT ベアラー認証を使うアプリがあります。 場合によっては、複数インスタンスの認証ハンドラーを持つアプリもあります。 たとえば、2 つの cookie ハンドラーがあり、1 つは基本 ID を含み、1 つは多要素認証 (MFA) がトリガーされたときに作成されます。 MFA がトリガーされるのは、追加のセキュリティを必要とする操作をユーザーが要求した場合などです。 MFA を必要とするリソースをユーザーが要求したときに MFA を実施する方法については、GitHub の問題「Protect section with MFA (MFA を使ってセクションを保護する)」を参照してください。

認証スキームは、認証中に認証サービスを構成するときに名前が付けられます。 次に例を示します。

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

先ほどのコードでは、cookie 用とベアラー用の 2 つの認証ハンドラーを追加しました。

注意

既定のスキームを指定すると、HttpContext.User プロパティにその ID が設定されます。 この動作が好ましくない場合は、AddAuthentication のパラメーターなしのフォームを呼び出して無効にします。

Authorize 属性を使ったスキームの選択

認可の時点で、使われるハンドラーがアプリから示されます。 認証スキームのコンマ区切りリストを [Authorize] に渡すことで、アプリに認可されるハンドラーを選びます。 既定値が構成されているかどうかにかかわらず、使う認証スキームを 1 つ以上 [Authorize] 属性に指定します。 次に例を示します。

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

前の例では、cookie とベアラーの両方のハンドラーが実行されるので、現在のユーザーの ID を作成して追加する機会があります。 1 つのスキームのみを指定すると、対応するハンドラーが実行されます。

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

前のコードでは、"ベアラー" スキームのハンドラーのみが実行されます。 cookie ベースの ID はすべて無視されます。

ポリシーを使ったスキームの選択

ポリシーで目的のスキームを指定したい場合は、ポリシーの追加時に AuthenticationSchemes コレクションを設定できます。

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

前の例では、"ベアラー" ハンドラーによって作成された ID に対してのみ、"Over18" ポリシーが実行されます。 ポリシーを使うには、[Authorize] 属性の Policy プロパティを設定します。

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

複数の認証スキームを使う

アプリによっては、複数の種類の認証をサポートする必要があります。 たとえば、アプリで Azure Active Directory とユーザー データベースのユーザーを認証する場合があります。 また、Active Directory フェデレーション サービスと Azure Active Directory B2C の両方のユーザーを認証するアプリの例もあります。 この場合、アプリは複数の発行元からの JWT ベアラー トークンを受け取る必要があります。

受け取るすべての認証スキームを追加してください。 たとえば、Startup.ConfigureServices の次のコードでは、発行元の異なる 2 つの JWT ベアラー認証スキームを追加しています。

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

注意

既定の認証スキーム JwtBearerDefaults.AuthenticationScheme には、1 つの JWT ベアラー認証のみが登録されています。 追加の認証は、固有の認証スキームに登録する必要があります。

次の手順は、両方の認証スキームを受け取るように既定の認可ポリシーを更新することです。 次に例を示します。

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

既定の認可ポリシーがオーバーライドされたので、コントローラーで [Authorize] 属性を使うことができます。 これで、コントローラーは、最初または 2 つ目の発行元から発行された JWT を使った要求を受け取るようになります。

複数の認証スキームの使用に関するこの GitHub の問題を参照してください。