ASP.NET Core でのリソース ベースの認可

認可方法はリソースによって異なります。 たとえば、ドキュメントの作成者だけがドキュメントの更新を承認されます。 そのため、認可の評価を行う前に、データ ストアからドキュメントを取得する必要があります。

属性の評価は、データ バインディングの前で、ドキュメントを読み込むページ ハンドラーまたはアクションの実行前に行われます。 これらの理由から、[Authorize] 属性での宣言型の認可では不十分です。 代わりに、カスタムの認可メソッド ("命令型の認可" と呼ばれるスタイル) を呼び出すことができます。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

承認によって保護されたユーザー データでの ASP.NET Core アプリの作成」には、リソース ベースの認可を使ったサンプル アプリが含まれています。

強制認可を使う

認可は IAuthorizationService サービスとして実装され、アプリケーションの起動時にサービス コレクションに登録されます。 サービスは、ページ ハンドラーまたはアクションへの依存関係の挿入によって利用できるようになります。

public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

    public DocumentController(IAuthorizationService authorizationService,
                              IDocumentRepository documentRepository)
    {
        _authorizationService = authorizationService;
        _documentRepository = documentRepository;
    }

IAuthorizationService には 2 つの AuthorizeAsync メソッド オーバーロードがあります。1 つはリソースとポリシー名を受けとり、もう 1 つはリソースと評価する要件の一覧を受けとります。

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
                          object resource,
                          IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
                          object resource,
                          string policyName);

次の例では、セキュリティで保護されるリソースがカスタム Document オブジェクトに読み込まれます。 指定されたドキュメントを現在のユーザーが編集できるかどうか判断するために AuthorizeAsync オーバーロードが呼び出されます。 カスタムの "EditPolicy" 認可ポリシーが判断に考慮されます。 認可ポリシーの作成の詳細については、「カスタム ポリシーベースの承認」を参照してください。

注意

次のコード サンプルでは、認証が実行され、User プロパティが設定されていることが前提となっています。

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, "EditPolicy");

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

リソースベースのハンドラーを記述する

リソースベースの認可用のハンドラーの記述は、シンプルな要件ハンドラーを記述するのと大きな違いはありません。 カスタム要件クラスを作成し、要件ハンドラー クラスを実装します。 要件クラスの作成の詳細については、「要件」を参照してください。

ハンドラー クラスでは、要件とリソースの種類の両方を指定します。 たとえば、SameAuthorRequirementDocument リソースを使うハンドラーは次のようになります。

public class DocumentAuthorizationHandler : 
    AuthorizationHandler<SameAuthorRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   SameAuthorRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

前の例では、SameAuthorRequirement がより汎用的な SpecificAuthorRequirement クラスの特殊なケースであると仮定します。 SpecificAuthorRequirement クラス (ここでは示されていません) には、作成者の名前を表す Name プロパティが含まれています。 Name プロパティは、現在のユーザーに設定できます。

Program.cs で要件とハンドラーを登録します。

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

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));
});

builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
builder.Services.AddScoped<IDocumentRepository, DocumentRepository>();

運用上の要件

CRUD (作成、読み取り、更新、削除) 操作の結果に基づいて決定を行う場合は、OperationAuthorizationRequirement ヘルパー クラスを使います。 このクラスを使うと、操作の種類ごとに個別のクラスで記述するのではなく、1 つのハンドラーで記述できます。 使うときには、いくつかの操作名を指定します。

public static class Operations
{
    public static OperationAuthorizationRequirement Create =
        new OperationAuthorizationRequirement { Name = nameof(Create) };
    public static OperationAuthorizationRequirement Read =
        new OperationAuthorizationRequirement { Name = nameof(Read) };
    public static OperationAuthorizationRequirement Update =
        new OperationAuthorizationRequirement { Name = nameof(Update) };
    public static OperationAuthorizationRequirement Delete =
        new OperationAuthorizationRequirement { Name = nameof(Delete) };
}

ハンドラーは、OperationAuthorizationRequirement 要件と Document リソースを使って次のように実装されます。

public class DocumentAuthorizationCrudHandler :
    AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   OperationAuthorizationRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author &&
            requirement.Name == Operations.Read.Name)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

上のハンドラーは、リソース、ユーザーの ID、要件の Name プロパティを使って操作を検証します。

運用リソース ハンドラーを使ったチャレンジと禁止

このセクションでは、チャレンジと禁止アクションの結果がどのように処理されるかについて、およびチャレンジと禁止の違いについて説明します。

操作リソース ハンドラーを呼び出すには、ページ ハンドラーまたはアクションで AuthorizeAsync を呼び出す際に操作を指定します。 次の例では、認証されたユーザーが指定されたドキュメントを表示できるかどうかを判断します。

注意

次のコード サンプルでは、認証が実行され、User プロパティが設定されていることが前提となっています。

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, Operations.Read);

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

認可が成功した場合、ドキュメントを表示するためのページが返されます。 認可が失敗してもユーザーが認証されている場合は、ForbidResult が返されて、認証ミドルウェアに認可が失敗したことが通知されます。 認証を実行する必要がある場合は、ChallengeResult が返されます。 対話型のブラウザー クライアントの場合は、ユーザーをログイン ページにリダイレクトすることが適切な場合があります。

認可方法はリソースによって異なります。 たとえば、ドキュメントの作成者だけがドキュメントの更新を承認されます。 そのため、認可の評価を行う前に、データ ストアからドキュメントを取得する必要があります。

属性の評価は、データ バインディングの前で、ドキュメントを読み込むページ ハンドラーまたはアクションの実行前に行われます。 これらの理由から、[Authorize] 属性での宣言型の認可では不十分です。 代わりに、カスタムの認可メソッド ("命令型の認可" と呼ばれるスタイル) を呼び出すことができます。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

承認によって保護されたユーザー データでの ASP.NET Core アプリの作成」には、リソース ベースの認可を使ったサンプル アプリが含まれています。

強制認可を使う

認可は IAuthorizationService サービスとして実装され、Startup クラス内のサービス コレクションに登録されます。 サービスは、ページ ハンドラーまたはアクションへの依存関係の挿入によって利用できるようになります。

public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

    public DocumentController(IAuthorizationService authorizationService,
                              IDocumentRepository documentRepository)
    {
        _authorizationService = authorizationService;
        _documentRepository = documentRepository;
    }

IAuthorizationService には 2 つの AuthorizeAsync メソッド オーバーロードがあります。1 つはリソースとポリシー名を受けとり、もう 1 つはリソースと評価する要件の一覧を受けとります。

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
                          object resource,
                          IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
                          object resource,
                          string policyName);

次の例では、セキュリティで保護されるリソースがカスタム Document オブジェクトに読み込まれます。 指定されたドキュメントを現在のユーザーが編集できるかどうか判断するために AuthorizeAsync オーバーロードが呼び出されます。 カスタムの "EditPolicy" 認可ポリシーが判断に考慮されます。 認可ポリシーの作成の詳細については、「カスタム ポリシーベースの承認」を参照してください。

注意

次のコード サンプルでは、認証が実行され、User プロパティが設定されていることが前提となっています。

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, "EditPolicy");

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

リソースベースのハンドラーを記述する

リソースベースの認可用のハンドラーの記述は、シンプルな要件ハンドラーを記述するのと大きな違いはありません。 カスタム要件クラスを作成し、要件ハンドラー クラスを実装します。 要件クラスの作成の詳細については、「要件」を参照してください。

ハンドラー クラスでは、要件とリソースの種類の両方を指定します。 たとえば、SameAuthorRequirementDocument リソースを使うハンドラーは次のようになります。

public class DocumentAuthorizationHandler : 
    AuthorizationHandler<SameAuthorRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   SameAuthorRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

前の例では、SameAuthorRequirement がより汎用的な SpecificAuthorRequirement クラスの特殊なケースであると仮定します。 SpecificAuthorRequirement クラス (ここでは示されていません) には、作成者の名前を表す Name プロパティが含まれています。 Name プロパティは、現在のユーザーに設定できます。

Startup.ConfigureServices で要件とハンドラーを登録します。

services.AddControllersWithViews();
services.AddRazorPages();

services.AddAuthorization(options =>
{
    options.AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));
});

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();

運用上の要件

CRUD (作成、読み取り、更新、削除) 操作の結果に基づいて決定を行う場合は、OperationAuthorizationRequirement ヘルパー クラスを使います。 このクラスを使うと、操作の種類ごとに個別のクラスで記述するのではなく、1 つのハンドラーで記述できます。 使うときには、いくつかの操作名を指定します。

public static class Operations
{
    public static OperationAuthorizationRequirement Create =
        new OperationAuthorizationRequirement { Name = nameof(Create) };
    public static OperationAuthorizationRequirement Read =
        new OperationAuthorizationRequirement { Name = nameof(Read) };
    public static OperationAuthorizationRequirement Update =
        new OperationAuthorizationRequirement { Name = nameof(Update) };
    public static OperationAuthorizationRequirement Delete =
        new OperationAuthorizationRequirement { Name = nameof(Delete) };
}

ハンドラーは、OperationAuthorizationRequirement 要件と Document リソースを使って次のように実装されます。

public class DocumentAuthorizationCrudHandler :
    AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   OperationAuthorizationRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author &&
            requirement.Name == Operations.Read.Name)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

上のハンドラーは、リソース、ユーザーの ID、要件の Name プロパティを使って操作を検証します。

運用リソース ハンドラーを使ったチャレンジと禁止

このセクションでは、チャレンジと禁止アクションの結果がどのように処理されるかについて、およびチャレンジと禁止の違いについて説明します。

操作リソース ハンドラーを呼び出すには、ページ ハンドラーまたはアクションで AuthorizeAsync を呼び出す際に操作を指定します。 次の例では、認証されたユーザーが指定されたドキュメントを表示できるかどうかを判断します。

注意

次のコード サンプルでは、認証が実行され、User プロパティが設定されていることが前提となっています。

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, Operations.Read);

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

認可が成功した場合、ドキュメントを表示するためのページが返されます。 認可が失敗してもユーザーが認証されている場合は、ForbidResult が返されて、認証ミドルウェアに認可が失敗したことが通知されます。 認証を実行する必要がある場合は、ChallengeResult が返されます。 対話型のブラウザー クライアントの場合は、ユーザーをログイン ページにリダイレクトすることが適切な場合があります。

認可方法はリソースによって異なります。 たとえば、ドキュメントの作成者だけがドキュメントの更新を承認されます。 そのため、認可の評価を行う前に、データ ストアからドキュメントを取得する必要があります。

属性の評価は、データ バインディングの前で、ドキュメントを読み込むページ ハンドラーまたはアクションの実行前に行われます。 これらの理由から、[Authorize] 属性での宣言型の認可では不十分です。 代わりに、カスタムの認可メソッド ("命令型の認可" と呼ばれるスタイル) を呼び出すことができます。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

承認によって保護されたユーザー データでの ASP.NET Core アプリの作成」には、リソース ベースの認可を使ったサンプル アプリが含まれています。

強制認可を使う

認可は IAuthorizationService サービスとして実装され、Startup クラス内のサービス コレクションに登録されます。 サービスは、ページ ハンドラーまたはアクションへの依存関係の挿入によって利用できるようになります。

public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

    public DocumentController(IAuthorizationService authorizationService,
                              IDocumentRepository documentRepository)
    {
        _authorizationService = authorizationService;
        _documentRepository = documentRepository;
    }

IAuthorizationService には 2 つの AuthorizeAsync メソッド オーバーロードがあります。1 つはリソースとポリシー名を受けとり、もう 1 つはリソースと評価する要件の一覧を受けとります。

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
                          object resource,
                          IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
                          object resource,
                          string policyName);

次の例では、セキュリティで保護されるリソースがカスタム Document オブジェクトに読み込まれます。 指定されたドキュメントを現在のユーザーが編集できるかどうか判断するために AuthorizeAsync オーバーロードが呼び出されます。 カスタムの "EditPolicy" 認可ポリシーが判断に考慮されます。 認可ポリシーの作成の詳細については、「カスタム ポリシーベースの承認」を参照してください。

注意

次のコード サンプルでは、認証が実行され、User プロパティが設定されていることが前提となっています。

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, "EditPolicy");

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

リソースベースのハンドラーを記述する

リソースベースの認可用のハンドラーの記述は、シンプルな要件ハンドラーを記述するのと大きな違いはありません。 カスタム要件クラスを作成し、要件ハンドラー クラスを実装します。 要件クラスの作成の詳細については、「要件」を参照してください。

ハンドラー クラスでは、要件とリソースの種類の両方を指定します。 たとえば、SameAuthorRequirementDocument リソースを使うハンドラーは次のようになります。

public class DocumentAuthorizationHandler : 
    AuthorizationHandler<SameAuthorRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   SameAuthorRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

前の例では、SameAuthorRequirement がより汎用的な SpecificAuthorRequirement クラスの特殊なケースであると仮定します。 SpecificAuthorRequirement クラス (ここでは示されていません) には、作成者の名前を表す Name プロパティが含まれています。 Name プロパティは、現在のユーザーに設定できます。

Startup.ConfigureServices で要件とハンドラーを登録します。

services.AddMvc();

services.AddAuthorization(options =>
{
    options.AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));
});

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();

運用上の要件

CRUD (作成、読み取り、更新、削除) 操作の結果に基づいて決定を行う場合は、OperationAuthorizationRequirement ヘルパー クラスを使います。 このクラスを使うと、操作の種類ごとに個別のクラスで記述するのではなく、1 つのハンドラーで記述できます。 使うときには、いくつかの操作名を指定します。

public static class Operations
{
    public static OperationAuthorizationRequirement Create =
        new OperationAuthorizationRequirement { Name = nameof(Create) };
    public static OperationAuthorizationRequirement Read =
        new OperationAuthorizationRequirement { Name = nameof(Read) };
    public static OperationAuthorizationRequirement Update =
        new OperationAuthorizationRequirement { Name = nameof(Update) };
    public static OperationAuthorizationRequirement Delete =
        new OperationAuthorizationRequirement { Name = nameof(Delete) };
}

ハンドラーは、OperationAuthorizationRequirement 要件と Document リソースを使って次のように実装されます。

public class DocumentAuthorizationCrudHandler :
    AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   OperationAuthorizationRequirement requirement,
                                                   Document resource)
    {
        if (context.User.Identity?.Name == resource.Author &&
            requirement.Name == Operations.Read.Name)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

上のハンドラーは、リソース、ユーザーの ID、要件の Name プロパティを使って操作を検証します。

運用リソース ハンドラーを使ったチャレンジと禁止

このセクションでは、チャレンジと禁止アクションの結果がどのように処理されるかについて、およびチャレンジと禁止の違いについて説明します。

操作リソース ハンドラーを呼び出すには、ページ ハンドラーまたはアクションで AuthorizeAsync を呼び出す際に操作を指定します。 次の例では、認証されたユーザーが指定されたドキュメントを表示できるかどうかを判断します。

注意

次のコード サンプルでは、認証が実行され、User プロパティが設定されていることが前提となっています。

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, Operations.Read);

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

認可が成功した場合、ドキュメントを表示するためのページが返されます。 認可が失敗してもユーザーが認証されている場合は、ForbidResult が返されて、認証ミドルウェアに認可が失敗したことが通知されます。 認証を実行する必要がある場合は、ChallengeResult が返されます。 対話型のブラウザー クライアントの場合は、ユーザーをログイン ページにリダイレクトすることが適切な場合があります。