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?
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.
First, you have to register your policy in the ConfigureServices() method of the Startup class, as part of the authorization service configuration.
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.
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.
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
AuthorizationHandlerContext
all registered IAuthorizationHandler
handlers are evaluatedHandleAsync
is calledAuthorizationHandler<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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With