Create Your Own Authorization Requirements In ASP.NET Core
ASP.NET Core provides a clean and simple model to express the application authorization rules in code: the Policy-based authorization model. This one is based on the following object model structure:
- Requirement: provides the intrinsic data of the authorization requirement
- Handler: defines how to handle the requirement
- Policy: represents the entry point of a single or a group of authorization requirements
Authorization policies can be configured in the Startup/ConfigurationServices method and can then be applied using theAuthorizeAttribute
attribute or theAuthorizeFilter
filter
For instance, here is an authorization policy configured with a very common requirement specifying that the current user must be authenticated with the default authentication scheme:
options.AddPolicy("my-policy", builder => builder.RequireAuthenticatedUser());
Let's see how this model works and how to create our own authorization requirement. I will take the previous sample as a starter point because of its simple and relevant aspect. So let's take a look at the code on github:
RequireAuthenticatedUser
is one of the core requirements embedded within the AuthorizationPolicyBuilder class which is the tool to use to create an authorization policy- The method just add the
DenyAnonymousAuthorizationRequirement
requirement to the policy requirements collection:
public AuthorizationPolicyBuilder RequireAuthenticatedUser()
{
Requirements.Add(new DenyAnonymousAuthorizationRequirement());
return this;
}
- Looking at the DenyAnonymousAuthorizationRequirement class definition:
public class DenyAnonymousAuthorizationRequirement :
AuthorizationHandler<DenyAnonymousAuthorizationRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement)
{
var user = context.User;
var userIsAnonymous = user?.Identity == null || !user.Identities.Any(i => i.IsAuthenticated);
if (!userIsAnonymous)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
We can see that the requirement implements the IAuthorizationRequirement
interface, to define the current class as a requirement, and inherits from the AuthorizationHandler<TRequirement>
class. This class exposes an abstract method HandleRequirementAsync
that receives an AuthorizationHandlerContext
as well as the requirement data to evaluate the status of the requirement if its associated policy is activated.
Here the implementation set the status to Succeed if one of the current user identities is authenticated.
So to define your own authorization requirement, you just have to create a IAuthorizationRequirement
requirement class and its associated AuthorizationHandler<TRequirement>
implementation and add the requirement to a policy requirements collection. Easy peasy no ?!
Yes but here, Microsoft took a shortcut for the AuthorizationHandler<DenyAnonymousAuthorizationRequirement>
implementation by defining it within the requirement definition. They are right because it will make the use of the most common requirements easier so it will make your life easier :) but how to decorrelate the handler definition for a custom requirement ?
The answer is straightforward: Dependency Injection.
Indeed you can keep your requirement data on one side and the handler implementation on the other side by registering your handler in your DI container.
Let's say that we have two different authorization requirements OneRequirement
and AnotherRequirement
. To express them as an authorization rule, we will create a dedicated policy for each of them. An extension method an AuthorizationPolicyBuilder
is an elegant way to do it ;) :
public static AuthorizationPolicyBuilder RequireOneThing(this AuthorizationPolicyBuilder builder, string parameter)
{
return builder.AddRequirements(new OneRequirement(parameter));
}
public static AuthorizationPolicyBuilder RequireAnotherThing(this AuthorizationPolicyBuilder builder, string parameter1, string parameter2)
{
return builder.AddRequirements(new TwoRequirement(parameter1, parameter2));
}
Now to handle these requirements in a decorrelated way, we will register the associated handlers in the DI container:
public static IServiceCollection AddCustomAuthorizationHandlers(this IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IAuthorizationHandler, OneAuthorizationHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, AnotherAuthorizationHandler>();
return serviceCollection;
}
Now if a policy uses one of our requirements (by invoking the RequireOneThing
or the RequireAnotherThing
method), the associated handlers will be automatically invoked if the policy is activated.
The ASP.NET Core Policy-based authorization model is really great and very flexible so you should give it a try even for simple authorization scenarios ;)
If you want more information about this model, go take a look at the official documentation. Have fun!