AspNetCore.Docs/aspnetcore/security/authorization/policies.md

9.1 KiB

title author description manager ms.author ms.custom ms.date ms.prod ms.technology ms.topic uid
Policy-based authorization in ASP.NET Core rick-anderson Learn how to create and use authorization policy handlers for enforcing authorization requirements in an ASP.NET Core app. wpickett riande mvc 11/21/2017 asp.net-core aspnet article security/authorization/policies

Policy-based authorization in ASP.NET Core

Underneath the covers, role-based authorization and claims-based authorization use a requirement, a requirement handler, and a pre-configured policy. These building blocks support the expression of authorization evaluations in code. The result is a richer, reusable, testable authorization structure.

An authorization policy consists of one or more requirements. It's registered as part of the authorization service configuration, in the Startup.ConfigureServices method:

[!code-csharp]

In the preceding example, an "AtLeast21" policy is created. It has a single requirement—that of a minimum age, which is supplied as a parameter to the requirement.

Policies are applied by using the [Authorize] attribute with the policy name. For example:

[!code-csharp]

Requirements

An authorization requirement is a collection of data parameters that a policy can use to evaluate the current user principal. In our "AtLeast21" policy, the requirement is a single parameter—the minimum age. A requirement implements IAuthorizationRequirement, which is an empty marker interface. A parameterized minimum age requirement could be implemented as follows:

[!code-csharp]

[!NOTE] A requirement doesn't need to have data or properties.

Authorization handlers

An authorization handler is responsible for the evaluation of a requirement's properties. The authorization handler evaluates the requirements against a provided AuthorizationHandlerContext to determine if access is allowed.

A requirement can have multiple handlers. A handler may inherit AuthorizationHandler<TRequirement>, where TRequirement is the requirement to be handled. Alternatively, a handler may implement IAuthorizationHandler to handle more than one type of requirement.

Use a handler for one requirement

The following is an example of a one-to-one relationship in which a minimum age handler utilizes a single requirement:

[!code-csharp]

The preceding code determines if the current user principal has a date of birth claim which has been issued by a known and trusted Issuer. Authorization can't occur when the claim is missing, in which case a completed task is returned. When a claim is present, the user's age is calculated. If the user meets the minimum age defined by the requirement, authorization is deemed successful. When authorization is successful, context.Succeed is invoked with the satisfied requirement as its sole parameter.

Use a handler for multiple requirements

The following is an example of a one-to-many relationship in which a permission handler utilizes three requirements:

[!code-csharp]

The preceding code traverses PendingRequirements—a property containing requirements not marked as successful. If the user has read permission, he or she must be either an owner or a sponsor to access the requested resource. If the user has edit or delete permission, he or she must be an owner to access the requested resource. When authorization is successful, context.Succeed is invoked with the satisfied requirement as its sole parameter.

Handler registration

Handlers are registered in the services collection during configuration. For example:

[!code-csharp]

Each handler is added to the services collection by invoking services.AddSingleton<IAuthorizationHandler, YourHandlerClass>();.

What should a handler return?

Note that the Handle method in the handler example returns no value. How is a status of either success or failure indicated?

  • A handler indicates success by calling context.Succeed(IAuthorizationRequirement requirement), passing the requirement that has been successfully validated.

  • A handler doesn't need to handle failures generally, as other handlers for the same requirement may succeed.

  • To guarantee failure, even if other requirement handlers succeed, call context.Fail.

When set to false, the InvokeHandlersAfterFailure property (available in ASP.NET Core 1.1 and later) short-circuits the execution of handlers when context.Fail is called. InvokeHandlersAfterFailure defaults to true, in which case all handlers are called. This allows requirements to produce side effects, such as logging, which always take place even if context.Fail has been called in another handler.

Why would I want multiple handlers for a requirement?

In cases where you want evaluation to be on an OR basis, implement multiple handlers for a single requirement. For example, Microsoft has doors which only open with key cards. If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. In this scenario, you'd have a single requirement, BuildingEntry, but multiple handlers, each one examining a single requirement.

BuildingEntryRequirement.cs

[!code-csharp]

BadgeEntryHandler.cs

[!code-csharp]

TemporaryStickerHandler.cs

[!code-csharp]

Ensure that both handlers are registered. If either handler succeeds when a policy evaluates the BuildingEntryRequirement, the policy evaluation succeeds.

Using a func to fulfill a policy

There may be situations in which fulfilling a policy is simple to express in code. It's possible to supply a Func<AuthorizationHandlerContext, bool> when configuring your policy with the RequireAssertion policy builder.

For example, the previous BadgeEntryHandler could be rewritten as follows:

[!code-csharp]

Accessing MVC request context in handlers

The HandleRequirementAsync method you implement in an authorization handler has two parameters: an AuthorizationHandlerContext and the TRequirement you are handling. Frameworks such as MVC or Jabbr are free to add any object to the Resource property on the AuthorizationHandlerContext to pass extra information.

For example, MVC passes an instance of AuthorizationFilterContext in the Resource property. This property provides access to HttpContext, RouteData, and everything else provided by MVC and Razor Pages.

The use of the Resource property is framework specific. Using information in the Resource property limits your authorization policies to particular frameworks. You should cast the Resource property using the as keyword, and then confirm the cast has succeed to ensure your code doesn't crash with an InvalidCastException when run on other frameworks:

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}