9.0 KiB
title | author | description | ms.author | ms.custom | ms.date | 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. | riande | mvc | 11/21/2017 | 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:
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:
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:
[!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:
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:
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:
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
BadgeEntryHandler.cs
TemporaryStickerHandler.cs
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:
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.
}