Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IAuthorizationHandler with multiple registration - how the dependency resolver selects the right implementation?

Consider the following code in the ConfigureServices method of the Startup class -

services.AddTransient<IAuthorizationHandler, BlockUsersHandler>();
services.AddTransient<IAuthorizationHandler, BlockClaimHandler>();

services.AddAuthorization(option =>
{                
    option.AddPolicy("NotHacker", policy =>
    {
        policy.AddRequirements(new BlockUsersRequirement("Hacker"));
    });                
    option.AddPolicy("NotThatClaim", policy =>
    {
        policy.AddRequirements(new BlockClaimRequirement(new Claim("ThatClaimType", "ThatClaim")));
    });
});

and these are the custom class implementations -

public class BlockUsersRequirement : IAuthorizationRequirement
{
    // Code goes here
}

public class BlockUsersHandler : AuthorizationHandler<BlockUsersRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BlockUsersRequirement requirement)
    {
        // Code goes here
    }
}

public class BlockClaimRequirement : IAuthorizationRequirement
{
    // Code goes here
}

public class BlockClaimHandler : AuthorizationHandler<BlockClaimRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BlockClaimRequirement requirement)
    {            
        // Code goes here
    }
}

My understanding was that whenever a dependency on a service is faced, the built-in dependency resolver provides the concrete implementation registered for that service and if I register multiple implementation of a service, then the last registration will take effect.

In the code above, two implementations are registered for IAuthorizationHandler and the two authorization policies are working fine with that.

So, how is the dependency resolver deciding when to select which implementation? And based on what?

EDIT - 2019.07.28
So, as @Martin answered below, looks like the dependency resolver can infer the implementation from the IAuthorizationRequirement in the AuthorizationHandler<TRequirement>, from which the Handler implementations are deriving.

But you actually can create a Handler class by directly implementing the IAuthorizationHandler interface without deriving from AuthorizationHandler<TRequirement> -

public class DeletePermissionRequirement : IAuthorizationRequirement
{
    // Nothing here
}

public class DeletePermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        // Code goes here
    }
}

So, now there is no IAuthorizationRequirement in the Handler's signature to infer from.

Also, you can add multiple Handler implementations for a single Requirement -

public class BuildingEntryRequirement : IAuthorizationRequirement
{
    // Nothing here
}

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        // Code goes here
    }
}

public class TemporaryPassHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        // Code goes here
    }
}

Taking the these new implementations into account the code in the ConfigureServices method looks like -

services.AddTransient<IAuthorizationHandler, BlockUsersHandler>();
services.AddTransient<IAuthorizationHandler, BlockClaimHandler>();
services.AddTransient<IAuthorizationHandler, DeletePermissionHandler>();
services.AddTransient<IAuthorizationHandler, BadgeEntryHandler>();
services.AddTransient<IAuthorizationHandler, TemporaryPassHandler>();

services.AddAuthorization(option =>
{                
    option.AddPolicy("NotHacker", policy =>
    {
        policy.AddRequirements(new BlockUsersRequirement("Hacker"));
    });                
    option.AddPolicy("NotThatClaim", policy =>
    {
        policy.AddRequirements(new BlockClaimRequirement(new Claim("ThatClaimType", "ThatClaim")));
    });
    option.AddPolicy("CanDelete", policy =>
    {
        policy.AddRequirements(new DeletePermissionRequirement());
    });
    option.AddPolicy("BadgeEntry", policy =>
    {
        policy.AddRequirements(new BuildingEntryRequirement());
    });
});

and, of course, all the authorization policies are working fine.

So again, how the dependency resolver is selecting the right implementation?

like image 511
atiyar Avatar asked Jul 27 '19 16:07

atiyar


People also ask

How to resolve dependency injection?

Resolve dependencies using IServiceProvider You can use the IServiceCollection interface to create a dependency injection container. Once the container has been created, the IServiceCollection instance is composed into an IServiceProvider instance. You can use this instance to resolve services.

Under Which method should a policy be registered for it to be a part of authorization service?

First, you have to register your policy in the ConfigureServices() method of the Startup class, as part of the authorization service configuration.

What is an advantage of using a policy-based authorization instead of a role based one?

By using Policy-based & Role-based Authorization process, we can provide access to particular area of application to the user based on the Role/Policy of the user.

How do I register a custom authorization filter in .NET core?

Right-click on the solution and add a new class. Enter the class name and click on Add. Next Inherite Attribute, IAuthorizationFilter to CustomAuthorization class which has overridden the OnAuthorization method. The OnAuthorization Method has the AuthorizationFilterContext parameter.


1 Answers

It is using the type of the requirement to decide which handler to use.

There can be more simultaneous authorization handlers that differ in type of the requirement.

It is possible to check more requirements in one policy.

Also when the authorization service is called, it picks the right handler:

IAuthorizationService _authorizationService; // injected

_authorizationService.AuthorizeAsync(User, resourceId, new MyAuthorizationRequirement(UserAccessScope.Account, resourceId, requiredRole));

Update:

The default behavior is

  • for a given AuthorizationHandlerContext all registered IAuthorizationHandler handlers are evaluated
  • for each of these handlers the method HandleAsync is called
  • a handler that derives from the abstract AuthorizationHandler<TRequirement> class implements the method HandleAsync this way:
public virtual async Task HandleAsync(AuthorizationHandlerContext context)
{
    foreach (var req in context.Requirements.OfType<TRequirement>())
    {
        await HandleRequirementAsync(context, req);
    }
}

It means the handler filters the requirements by type. It also means a handler that does not derive from the abstract class AuthorizationHandler<TRequirement> has its own implementation of the HandleAsync method.

The difference between an authorization handler that does inherit from the abstract class AuthorizationHandler<TRequirement> and one that doesn't boils down to how the HandleAsync method of the interface IAuthorizationHandler is implemented. AuthorizationHandler<TRequirement> has a default implementation described above, the implementor of the generic interface IAuthorizationHandler needs to supply its own implementation.

The answer to the second part concerning multiple Handler implementations for a single Requirement is, all handlers which have requirement of the given type will be evaluated, and if any one of them succeeds and none of them explicitly fails (Fail method has been called on the context) then the operation will be authorized.

like image 168
Martin Staufcik Avatar answered Sep 20 '22 02:09

Martin Staufcik